blob: 65b79fcf75bac840c78a221571a4b6e8cf7c51fc [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.widget;
18
Gilles Debunne78996c92010-10-12 16:01:47 -070019import android.R;
Dianne Hackborn1040dc42010-08-26 22:11:06 -070020import android.content.ClipData;
Gilles Debunne64e54a62010-09-07 19:07:17 -070021import android.content.ClipboardManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080022import android.content.Context;
Dianne Hackborn3aa49b62013-04-26 16:39:17 -070023import android.content.UndoManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080024import android.content.res.ColorStateList;
Christopher Tate1373a8e2011-11-10 19:59:13 -080025import android.content.res.CompatibilityInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026import android.content.res.Resources;
27import android.content.res.TypedArray;
28import android.content.res.XmlResourceParser;
29import android.graphics.Canvas;
Philip Milne7b757812012-09-19 18:13:44 -070030import android.graphics.Insets;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031import android.graphics.Paint;
32import android.graphics.Path;
33import android.graphics.Rect;
34import android.graphics.RectF;
35import android.graphics.Typeface;
36import android.graphics.drawable.Drawable;
Gilles Debunne64e54a62010-09-07 19:07:17 -070037import android.inputmethodservice.ExtractEditText;
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +090038import android.os.AsyncTask;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039import android.os.Bundle;
40import android.os.Handler;
svetoslavganov75986cf2009-05-14 22:28:01 -070041import android.os.Message;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080042import android.os.Parcel;
43import android.os.Parcelable;
44import android.os.SystemClock;
alanv7d624192012-05-21 14:23:17 -070045import android.provider.Settings;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080046import android.text.BoringLayout;
47import android.text.DynamicLayout;
48import android.text.Editable;
49import android.text.GetChars;
50import android.text.GraphicsOperations;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080051import android.text.InputFilter;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -070052import android.text.InputType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080053import android.text.Layout;
54import android.text.ParcelableSpan;
55import android.text.Selection;
56import android.text.SpanWatcher;
57import android.text.Spannable;
svetoslavganov75986cf2009-05-14 22:28:01 -070058import android.text.SpannableString;
Victoria Leaseaf7dcdf2013-10-24 12:35:42 -070059import android.text.SpannableStringBuilder;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080060import android.text.Spanned;
61import android.text.SpannedString;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080062import android.text.StaticLayout;
Doug Feltcb3791202011-07-07 11:57:48 -070063import android.text.TextDirectionHeuristic;
64import android.text.TextDirectionHeuristics;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080065import android.text.TextPaint;
66import android.text.TextUtils;
Adam Powell282e3772011-08-30 16:51:11 -070067import android.text.TextUtils.TruncateAt;
Gilles Debunne0eea6682011-08-29 13:30:31 -070068import android.text.TextWatcher;
Adam Powell7f8f79a2011-07-07 18:35:54 -070069import android.text.method.AllCapsTransformationMethod;
Gilles Debunne86b9c782010-11-11 10:43:48 -080070import android.text.method.ArrowKeyMovementMethod;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080071import android.text.method.DateKeyListener;
72import android.text.method.DateTimeKeyListener;
73import android.text.method.DialerKeyListener;
74import android.text.method.DigitsKeyListener;
75import android.text.method.KeyListener;
76import android.text.method.LinkMovementMethod;
77import android.text.method.MetaKeyKeyListener;
78import android.text.method.MovementMethod;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080079import android.text.method.PasswordTransformationMethod;
80import android.text.method.SingleLineTransformationMethod;
81import android.text.method.TextKeyListener;
svetoslavganov75986cf2009-05-14 22:28:01 -070082import android.text.method.TimeKeyListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080083import android.text.method.TransformationMethod;
Adam Powell7f8f79a2011-07-07 18:35:54 -070084import android.text.method.TransformationMethod2;
Gilles Debunne214a8622011-04-26 15:44:37 -070085import android.text.method.WordIterator;
Gilles Debunneb35ab7b2011-12-05 15:54:00 -080086import android.text.style.CharacterStyle;
Gilles Debunnef3895ed2010-12-21 12:53:58 -080087import android.text.style.ClickableSpan;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080088import android.text.style.ParagraphStyle;
Gilles Debunne6435a562011-08-04 21:22:30 -070089import android.text.style.SpellCheckSpan;
Gilles Debunne2037b822011-04-22 13:07:33 -070090import android.text.style.SuggestionSpan;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080091import android.text.style.URLSpan;
92import android.text.style.UpdateAppearance;
93import android.text.util.Linkify;
94import android.util.AttributeSet;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080095import android.util.FloatMath;
svetoslavganov75986cf2009-05-14 22:28:01 -070096import android.util.Log;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080097import android.util.TypedValue;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -070098import android.view.AccessibilityIterators.TextSegmentIterator;
Gilles Debunne27113f82010-08-23 12:09:14 -070099import android.view.ActionMode;
Gilles Debunnef170a342010-11-11 11:08:59 -0800100import android.view.DragEvent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800101import android.view.Gravity;
Gilles Debunnef788a9f2010-07-22 10:17:23 -0700102import android.view.HapticFeedbackConstants;
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800103import android.view.KeyCharacterMap;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800104import android.view.KeyEvent;
Gilles Debunnef788a9f2010-07-22 10:17:23 -0700105import android.view.Menu;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800106import android.view.MenuItem;
107import android.view.MotionEvent;
108import android.view.View;
Gilles Debunne65f60412010-10-15 16:18:35 -0700109import android.view.ViewConfiguration;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800110import android.view.ViewDebug;
Gilles Debunne27113f82010-08-23 12:09:14 -0700111import android.view.ViewGroup.LayoutParams;
Gilles Debunne3784a7f2011-07-15 13:49:38 -0700112import android.view.ViewRootImpl;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800113import android.view.ViewTreeObserver;
svetoslavganov75986cf2009-05-14 22:28:01 -0700114import android.view.accessibility.AccessibilityEvent;
115import android.view.accessibility.AccessibilityManager;
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700116import android.view.accessibility.AccessibilityNodeInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800117import android.view.animation.AnimationUtils;
118import android.view.inputmethod.BaseInputConnection;
119import android.view.inputmethod.CompletionInfo;
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -0800120import android.view.inputmethod.CorrectionInfo;
svetoslavganov75986cf2009-05-14 22:28:01 -0700121import android.view.inputmethod.EditorInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800122import android.view.inputmethod.ExtractedText;
123import android.view.inputmethod.ExtractedTextRequest;
124import android.view.inputmethod.InputConnection;
125import android.view.inputmethod.InputMethodManager;
satok05f24702011-11-02 19:29:35 +0900126import android.view.textservice.SpellCheckerSubtype;
127import android.view.textservice.TextServicesManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800128import android.widget.RemoteViews.RemoteView;
129
Gilles Debunne22378292011-08-12 10:38:52 -0700130import com.android.internal.util.FastMath;
131import com.android.internal.widget.EditableInputConnection;
132
133import org.xmlpull.v1.XmlPullParserException;
134
Gilles Debunne27113f82010-08-23 12:09:14 -0700135import java.io.IOException;
136import java.lang.ref.WeakReference;
137import java.util.ArrayList;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -0700138import java.util.Locale;
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;
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800362
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700363 mDrawableLeft = mDrawableEnd;
364 mDrawableSizeLeft = mDrawableSizeEnd;
365 mDrawableHeightLeft = mDrawableHeightEnd;
366 }
367 break;
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800368
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700369 case LAYOUT_DIRECTION_LTR:
370 default:
371 if (mOverride) {
372 mDrawableLeft = mDrawableStart;
373 mDrawableSizeLeft = mDrawableSizeStart;
374 mDrawableHeightLeft = mDrawableHeightStart;
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800375
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700376 mDrawableRight = mDrawableEnd;
377 mDrawableSizeRight = mDrawableSizeEnd;
378 mDrawableHeightRight = mDrawableHeightEnd;
379 }
380 break;
381 }
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800382 }
383 applyErrorDrawableIfNeeded(layoutDirection);
384 updateDrawablesLayoutDirection(layoutDirection);
385 }
386
387 private void updateDrawablesLayoutDirection(int layoutDirection) {
388 if (mDrawableLeft != null) {
389 mDrawableLeft.setLayoutDirection(layoutDirection);
390 }
391 if (mDrawableRight != null) {
392 mDrawableRight.setLayoutDirection(layoutDirection);
393 }
394 if (mDrawableTop != null) {
395 mDrawableTop.setLayoutDirection(layoutDirection);
396 }
397 if (mDrawableBottom != null) {
398 mDrawableBottom.setLayoutDirection(layoutDirection);
399 }
400 }
401
402 public void setErrorDrawable(Drawable dr, TextView tv) {
403 if (mDrawableError != dr && mDrawableError != null) {
404 mDrawableError.setCallback(null);
405 }
406 mDrawableError = dr;
407
408 final Rect compoundRect = mCompoundRect;
409 int[] state = tv.getDrawableState();
410
411 if (mDrawableError != null) {
412 mDrawableError.setState(state);
413 mDrawableError.copyBounds(compoundRect);
414 mDrawableError.setCallback(tv);
415 mDrawableSizeError = compoundRect.width();
416 mDrawableHeightError = compoundRect.height();
417 } else {
418 mDrawableSizeError = mDrawableHeightError = 0;
419 }
420 }
421
422 private void applyErrorDrawableIfNeeded(int layoutDirection) {
423 // first restore the initial state if needed
424 switch (mDrawableSaved) {
425 case DRAWABLE_LEFT:
426 mDrawableLeft = mDrawableTemp;
427 mDrawableSizeLeft = mDrawableSizeTemp;
428 mDrawableHeightLeft = mDrawableHeightTemp;
429 break;
430 case DRAWABLE_RIGHT:
431 mDrawableRight = mDrawableTemp;
432 mDrawableSizeRight = mDrawableSizeTemp;
433 mDrawableHeightRight = mDrawableHeightTemp;
434 break;
435 case DRAWABLE_NONE:
436 default:
437 }
438 // then, if needed, assign the Error drawable to the correct location
439 if (mDrawableError != null) {
440 switch(layoutDirection) {
441 case LAYOUT_DIRECTION_RTL:
442 mDrawableSaved = DRAWABLE_LEFT;
443
444 mDrawableTemp = mDrawableLeft;
445 mDrawableSizeTemp = mDrawableSizeLeft;
446 mDrawableHeightTemp = mDrawableHeightLeft;
447
448 mDrawableLeft = mDrawableError;
449 mDrawableSizeLeft = mDrawableSizeError;
450 mDrawableHeightLeft = mDrawableHeightError;
451 break;
452 case LAYOUT_DIRECTION_LTR:
453 default:
454 mDrawableSaved = DRAWABLE_RIGHT;
455
456 mDrawableTemp = mDrawableRight;
457 mDrawableSizeTemp = mDrawableSizeRight;
458 mDrawableHeightTemp = mDrawableHeightRight;
459
460 mDrawableRight = mDrawableError;
461 mDrawableSizeRight = mDrawableSizeError;
462 mDrawableHeightRight = mDrawableHeightError;
463 break;
464 }
465 }
466 }
Gilles Debunne60e21862012-01-30 15:04:14 -0800467 }
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800468
Gilles Debunned88876a2012-03-16 17:34:04 -0700469 Drawables mDrawables;
Gilles Debunne60e21862012-01-30 15:04:14 -0800470
471 private CharWrapper mCharWrapper;
472
473 private Marquee mMarquee;
474 private boolean mRestartMarquee;
475
476 private int mMarqueeRepeatLimit = 3;
477
Fabrice Di Meglio1957d282012-10-25 17:42:39 -0700478 private int mLastLayoutDirection = -1;
Gilles Debunne60e21862012-01-30 15:04:14 -0800479
480 /**
481 * On some devices the fading edges add a performance penalty if used
482 * extensively in the same layout. This mode indicates how the marquee
483 * is currently being shown, if applicable. (mEllipsize will == MARQUEE)
484 */
485 private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
486
487 /**
488 * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores
489 * the layout that should be used when the mode switches.
490 */
491 private Layout mSavedMarqueeModeLayout;
492
493 @ViewDebug.ExportedProperty(category = "text")
494 private CharSequence mText;
495 private CharSequence mTransformed;
496 private BufferType mBufferType = BufferType.NORMAL;
497
498 private CharSequence mHint;
499 private Layout mHintLayout;
500
501 private MovementMethod mMovement;
502
503 private TransformationMethod mTransformation;
504 private boolean mAllowTransformationLengthChange;
505 private ChangeWatcher mChangeWatcher;
506
507 private ArrayList<TextWatcher> mListeners;
508
509 // display attributes
510 private final TextPaint mTextPaint;
511 private boolean mUserSetTextScaleX;
512 private Layout mLayout;
513
514 private int mGravity = Gravity.TOP | Gravity.START;
515 private boolean mHorizontallyScrolling;
516
517 private int mAutoLinkMask;
518 private boolean mLinksClickable = true;
519
520 private float mSpacingMult = 1.0f;
521 private float mSpacingAdd = 0.0f;
522
523 private int mMaximum = Integer.MAX_VALUE;
524 private int mMaxMode = LINES;
525 private int mMinimum = 0;
526 private int mMinMode = LINES;
527
528 private int mOldMaximum = mMaximum;
529 private int mOldMaxMode = mMaxMode;
530
531 private int mMaxWidth = Integer.MAX_VALUE;
532 private int mMaxWidthMode = PIXELS;
533 private int mMinWidth = 0;
534 private int mMinWidthMode = PIXELS;
535
536 private boolean mSingleLine;
537 private int mDesiredHeightAtMeasure = -1;
538 private boolean mIncludePad = true;
Raph Levienf5c1a872012-10-15 17:22:26 -0700539 private int mDeferScroll = -1;
Gilles Debunne60e21862012-01-30 15:04:14 -0800540
541 // tmp primitives, so we don't alloc them on each draw
542 private Rect mTempRect;
543 private long mLastScroll;
544 private Scroller mScroller;
545
546 private BoringLayout.Metrics mBoring, mHintBoring;
547 private BoringLayout mSavedLayout, mSavedHintLayout;
548
549 private TextDirectionHeuristic mTextDir;
550
551 private InputFilter[] mFilters = NO_FILTERS;
552
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +0900553 private volatile Locale mCurrentSpellCheckerLocaleCache;
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +0900554
Gilles Debunne83051b82012-02-24 20:01:13 -0800555 // It is possible to have a selection even when mEditor is null (programmatically set, like when
556 // a link is pressed). These highlight-related fields do not go in mEditor.
Gilles Debunned88876a2012-03-16 17:34:04 -0700557 int mHighlightColor = 0x6633B5E5;
Gilles Debunne83051b82012-02-24 20:01:13 -0800558 private Path mHighlightPath;
559 private final Paint mHighlightPaint;
560 private boolean mHighlightPathBogus = true;
561
Gilles Debunne60e21862012-01-30 15:04:14 -0800562 // Although these fields are specific to editable text, they are not added to Editor because
563 // they are defined by the TextView's style and are theme-dependent.
Gilles Debunned88876a2012-03-16 17:34:04 -0700564 int mCursorDrawableRes;
Gilles Debunne60e21862012-01-30 15:04:14 -0800565 // These four fields, could be moved to Editor, since we know their default values and we
566 // could condition the creation of the Editor to a non standard value. This is however
567 // brittle since the hardcoded values here (such as
568 // com.android.internal.R.drawable.text_select_handle_left) would have to be updated if the
569 // default style is modified.
Gilles Debunned88876a2012-03-16 17:34:04 -0700570 int mTextSelectHandleLeftRes;
571 int mTextSelectHandleRightRes;
572 int mTextSelectHandleRes;
573 int mTextEditSuggestionItemLayout;
Gilles Debunne60e21862012-01-30 15:04:14 -0800574
575 /**
576 * EditText specific data, created on demand when one of the Editor fields is used.
Gilles Debunne5fae9962012-05-08 14:53:20 -0700577 * See {@link #createEditorIfNeeded()}.
Gilles Debunne60e21862012-01-30 15:04:14 -0800578 */
579 private Editor mEditor;
580
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800581 /*
582 * Kick-start the font cache for the zygote process (to pay the cost of
583 * initializing freetype for our default font only once).
584 */
585 static {
586 Paint p = new Paint();
587 p.setAntiAlias(true);
588 // We don't care about the result, just the side-effect of measuring.
589 p.measureText("H");
590 }
591
592 /**
593 * Interface definition for a callback to be invoked when an action is
594 * performed on the editor.
595 */
596 public interface OnEditorActionListener {
597 /**
598 * Called when an action is being performed.
599 *
600 * @param v The view that was clicked.
601 * @param actionId Identifier of the action. This will be either the
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700602 * identifier you supplied, or {@link EditorInfo#IME_NULL
603 * EditorInfo.IME_NULL} if being called due to the enter key
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800604 * being pressed.
605 * @param event If triggered by an enter key, this is the event;
606 * otherwise, this is null.
607 * @return Return true if you have consumed the action, else false.
608 */
609 boolean onEditorAction(TextView v, int actionId, KeyEvent event);
610 }
Gilles Debunne21078e42011-08-02 10:22:35 -0700611
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800612 public TextView(Context context) {
613 this(context, null);
614 }
615
Gilles Debunnec1714022012-01-17 13:59:23 -0800616 public TextView(Context context, AttributeSet attrs) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800617 this(context, attrs, com.android.internal.R.attr.textViewStyle);
618 }
619
Alan Viverette617feb92013-09-09 18:09:13 -0700620 public TextView(Context context, AttributeSet attrs, int defStyleAttr) {
621 this(context, attrs, defStyleAttr, 0);
622 }
623
Gilles Debunnee15b3582010-06-16 15:17:21 -0700624 @SuppressWarnings("deprecation")
Alan Viverette617feb92013-09-09 18:09:13 -0700625 public TextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
626 super(context, attrs, defStyleAttr, defStyleRes);
627
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800628 mText = "";
629
Christopher Tate1373a8e2011-11-10 19:59:13 -0800630 final Resources res = getResources();
631 final CompatibilityInfo compat = res.getCompatibilityInfo();
632
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800633 mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
Christopher Tate1373a8e2011-11-10 19:59:13 -0800634 mTextPaint.density = res.getDisplayMetrics().density;
635 mTextPaint.setCompatibilityScaling(compat.applicationScale);
Gilles Debunne8cbb4c62011-01-24 12:33:56 -0800636
Gilles Debunne83051b82012-02-24 20:01:13 -0800637 mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
638 mHighlightPaint.setCompatibilityScaling(compat.applicationScale);
639
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800640 mMovement = getDefaultMovementMethod();
Gilles Debunne60e21862012-01-30 15:04:14 -0800641
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800642 mTransformation = null;
643
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800644 int textColorHighlight = 0;
645 ColorStateList textColor = null;
646 ColorStateList textColorHint = null;
647 ColorStateList textColorLink = null;
648 int textSize = 15;
Raph Leviend570e892012-05-09 11:45:34 -0700649 String fontFamily = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800650 int typefaceIndex = -1;
651 int styleIndex = -1;
Adam Powell7f8f79a2011-07-07 18:35:54 -0700652 boolean allCaps = false;
Adam Powellac91df82013-02-14 13:48:47 -0800653 int shadowcolor = 0;
654 float dx = 0, dy = 0, r = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800655
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700656 final Resources.Theme theme = context.getTheme();
657
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800658 /*
659 * Look the appearance up without checking first if it exists because
660 * almost every TextView has one and it greatly simplifies the logic
661 * to be able to parse the appearance first and then let specific tags
662 * for this View override it.
663 */
Alan Viverette617feb92013-09-09 18:09:13 -0700664 TypedArray a = theme.obtainStyledAttributes(attrs,
665 com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800666 TypedArray appearance = null;
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700667 int ap = a.getResourceId(
668 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);
669 a.recycle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800670 if (ap != -1) {
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700671 appearance = theme.obtainStyledAttributes(
672 ap, com.android.internal.R.styleable.TextAppearance);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800673 }
674 if (appearance != null) {
675 int n = appearance.getIndexCount();
676 for (int i = 0; i < n; i++) {
677 int attr = appearance.getIndex(i);
678
679 switch (attr) {
680 case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
681 textColorHighlight = appearance.getColor(attr, textColorHighlight);
682 break;
683
684 case com.android.internal.R.styleable.TextAppearance_textColor:
685 textColor = appearance.getColorStateList(attr);
686 break;
687
688 case com.android.internal.R.styleable.TextAppearance_textColorHint:
689 textColorHint = appearance.getColorStateList(attr);
690 break;
691
692 case com.android.internal.R.styleable.TextAppearance_textColorLink:
693 textColorLink = appearance.getColorStateList(attr);
694 break;
695
696 case com.android.internal.R.styleable.TextAppearance_textSize:
697 textSize = appearance.getDimensionPixelSize(attr, textSize);
698 break;
699
700 case com.android.internal.R.styleable.TextAppearance_typeface:
701 typefaceIndex = appearance.getInt(attr, -1);
702 break;
703
Raph Leviend570e892012-05-09 11:45:34 -0700704 case com.android.internal.R.styleable.TextAppearance_fontFamily:
705 fontFamily = appearance.getString(attr);
706 break;
707
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800708 case com.android.internal.R.styleable.TextAppearance_textStyle:
709 styleIndex = appearance.getInt(attr, -1);
710 break;
Adam Powell7f8f79a2011-07-07 18:35:54 -0700711
712 case com.android.internal.R.styleable.TextAppearance_textAllCaps:
713 allCaps = appearance.getBoolean(attr, false);
714 break;
Adam Powellac91df82013-02-14 13:48:47 -0800715
716 case com.android.internal.R.styleable.TextAppearance_shadowColor:
717 shadowcolor = a.getInt(attr, 0);
718 break;
719
720 case com.android.internal.R.styleable.TextAppearance_shadowDx:
721 dx = a.getFloat(attr, 0);
722 break;
723
724 case com.android.internal.R.styleable.TextAppearance_shadowDy:
725 dy = a.getFloat(attr, 0);
726 break;
727
728 case com.android.internal.R.styleable.TextAppearance_shadowRadius:
729 r = a.getFloat(attr, 0);
730 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800731 }
732 }
733
734 appearance.recycle();
735 }
736
737 boolean editable = getDefaultEditable();
738 CharSequence inputMethod = null;
739 int numeric = 0;
740 CharSequence digits = null;
741 boolean phone = false;
742 boolean autotext = false;
743 int autocap = -1;
744 int buffertype = 0;
745 boolean selectallonfocus = false;
746 Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -0700747 drawableBottom = null, drawableStart = null, drawableEnd = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800748 int drawablePadding = 0;
749 int ellipsize = -1;
Gilles Debunnef95449d2010-11-05 13:54:13 -0700750 boolean singleLine = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800751 int maxlength = -1;
752 CharSequence text = "";
Romain Guy4dc4f732009-06-19 15:16:40 -0700753 CharSequence hint = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800754 boolean password = false;
755 int inputType = EditorInfo.TYPE_NULL;
756
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700757 a = theme.obtainStyledAttributes(
Alan Viverette617feb92013-09-09 18:09:13 -0700758 attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700759
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800760 int n = a.getIndexCount();
761 for (int i = 0; i < n; i++) {
762 int attr = a.getIndex(i);
763
764 switch (attr) {
765 case com.android.internal.R.styleable.TextView_editable:
766 editable = a.getBoolean(attr, editable);
767 break;
768
769 case com.android.internal.R.styleable.TextView_inputMethod:
770 inputMethod = a.getText(attr);
771 break;
772
773 case com.android.internal.R.styleable.TextView_numeric:
774 numeric = a.getInt(attr, numeric);
775 break;
776
777 case com.android.internal.R.styleable.TextView_digits:
778 digits = a.getText(attr);
779 break;
780
781 case com.android.internal.R.styleable.TextView_phoneNumber:
782 phone = a.getBoolean(attr, phone);
783 break;
784
785 case com.android.internal.R.styleable.TextView_autoText:
786 autotext = a.getBoolean(attr, autotext);
787 break;
788
789 case com.android.internal.R.styleable.TextView_capitalize:
790 autocap = a.getInt(attr, autocap);
791 break;
792
793 case com.android.internal.R.styleable.TextView_bufferType:
794 buffertype = a.getInt(attr, buffertype);
795 break;
796
797 case com.android.internal.R.styleable.TextView_selectAllOnFocus:
798 selectallonfocus = a.getBoolean(attr, selectallonfocus);
799 break;
800
801 case com.android.internal.R.styleable.TextView_autoLink:
802 mAutoLinkMask = a.getInt(attr, 0);
803 break;
804
805 case com.android.internal.R.styleable.TextView_linksClickable:
806 mLinksClickable = a.getBoolean(attr, true);
807 break;
808
809 case com.android.internal.R.styleable.TextView_drawableLeft:
810 drawableLeft = a.getDrawable(attr);
811 break;
812
813 case com.android.internal.R.styleable.TextView_drawableTop:
814 drawableTop = a.getDrawable(attr);
815 break;
816
817 case com.android.internal.R.styleable.TextView_drawableRight:
818 drawableRight = a.getDrawable(attr);
819 break;
820
821 case com.android.internal.R.styleable.TextView_drawableBottom:
822 drawableBottom = a.getDrawable(attr);
823 break;
824
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -0700825 case com.android.internal.R.styleable.TextView_drawableStart:
826 drawableStart = a.getDrawable(attr);
827 break;
828
829 case com.android.internal.R.styleable.TextView_drawableEnd:
830 drawableEnd = a.getDrawable(attr);
831 break;
832
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800833 case com.android.internal.R.styleable.TextView_drawablePadding:
834 drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
835 break;
836
837 case com.android.internal.R.styleable.TextView_maxLines:
838 setMaxLines(a.getInt(attr, -1));
839 break;
840
841 case com.android.internal.R.styleable.TextView_maxHeight:
842 setMaxHeight(a.getDimensionPixelSize(attr, -1));
843 break;
844
845 case com.android.internal.R.styleable.TextView_lines:
846 setLines(a.getInt(attr, -1));
847 break;
848
849 case com.android.internal.R.styleable.TextView_height:
850 setHeight(a.getDimensionPixelSize(attr, -1));
851 break;
852
853 case com.android.internal.R.styleable.TextView_minLines:
854 setMinLines(a.getInt(attr, -1));
855 break;
856
857 case com.android.internal.R.styleable.TextView_minHeight:
858 setMinHeight(a.getDimensionPixelSize(attr, -1));
859 break;
860
861 case com.android.internal.R.styleable.TextView_maxEms:
862 setMaxEms(a.getInt(attr, -1));
863 break;
864
865 case com.android.internal.R.styleable.TextView_maxWidth:
866 setMaxWidth(a.getDimensionPixelSize(attr, -1));
867 break;
868
869 case com.android.internal.R.styleable.TextView_ems:
870 setEms(a.getInt(attr, -1));
871 break;
872
873 case com.android.internal.R.styleable.TextView_width:
874 setWidth(a.getDimensionPixelSize(attr, -1));
875 break;
876
877 case com.android.internal.R.styleable.TextView_minEms:
878 setMinEms(a.getInt(attr, -1));
879 break;
880
881 case com.android.internal.R.styleable.TextView_minWidth:
882 setMinWidth(a.getDimensionPixelSize(attr, -1));
883 break;
884
885 case com.android.internal.R.styleable.TextView_gravity:
886 setGravity(a.getInt(attr, -1));
887 break;
888
889 case com.android.internal.R.styleable.TextView_hint:
Romain Guy4dc4f732009-06-19 15:16:40 -0700890 hint = a.getText(attr);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800891 break;
892
893 case com.android.internal.R.styleable.TextView_text:
894 text = a.getText(attr);
895 break;
896
897 case com.android.internal.R.styleable.TextView_scrollHorizontally:
898 if (a.getBoolean(attr, false)) {
899 setHorizontallyScrolling(true);
900 }
901 break;
902
903 case com.android.internal.R.styleable.TextView_singleLine:
904 singleLine = a.getBoolean(attr, singleLine);
905 break;
906
907 case com.android.internal.R.styleable.TextView_ellipsize:
908 ellipsize = a.getInt(attr, ellipsize);
909 break;
910
911 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
912 setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
913 break;
914
915 case com.android.internal.R.styleable.TextView_includeFontPadding:
916 if (!a.getBoolean(attr, true)) {
917 setIncludeFontPadding(false);
918 }
919 break;
920
921 case com.android.internal.R.styleable.TextView_cursorVisible:
922 if (!a.getBoolean(attr, true)) {
923 setCursorVisible(false);
924 }
925 break;
926
927 case com.android.internal.R.styleable.TextView_maxLength:
928 maxlength = a.getInt(attr, -1);
929 break;
930
931 case com.android.internal.R.styleable.TextView_textScaleX:
932 setTextScaleX(a.getFloat(attr, 1.0f));
933 break;
934
935 case com.android.internal.R.styleable.TextView_freezesText:
936 mFreezesText = a.getBoolean(attr, false);
937 break;
938
939 case com.android.internal.R.styleable.TextView_shadowColor:
940 shadowcolor = a.getInt(attr, 0);
941 break;
942
943 case com.android.internal.R.styleable.TextView_shadowDx:
944 dx = a.getFloat(attr, 0);
945 break;
946
947 case com.android.internal.R.styleable.TextView_shadowDy:
948 dy = a.getFloat(attr, 0);
949 break;
950
951 case com.android.internal.R.styleable.TextView_shadowRadius:
952 r = a.getFloat(attr, 0);
953 break;
954
955 case com.android.internal.R.styleable.TextView_enabled:
956 setEnabled(a.getBoolean(attr, isEnabled()));
957 break;
958
959 case com.android.internal.R.styleable.TextView_textColorHighlight:
960 textColorHighlight = a.getColor(attr, textColorHighlight);
961 break;
962
963 case com.android.internal.R.styleable.TextView_textColor:
964 textColor = a.getColorStateList(attr);
965 break;
966
967 case com.android.internal.R.styleable.TextView_textColorHint:
968 textColorHint = a.getColorStateList(attr);
969 break;
970
971 case com.android.internal.R.styleable.TextView_textColorLink:
972 textColorLink = a.getColorStateList(attr);
973 break;
974
975 case com.android.internal.R.styleable.TextView_textSize:
976 textSize = a.getDimensionPixelSize(attr, textSize);
977 break;
978
979 case com.android.internal.R.styleable.TextView_typeface:
980 typefaceIndex = a.getInt(attr, typefaceIndex);
981 break;
982
983 case com.android.internal.R.styleable.TextView_textStyle:
984 styleIndex = a.getInt(attr, styleIndex);
985 break;
986
Raph Leviend570e892012-05-09 11:45:34 -0700987 case com.android.internal.R.styleable.TextView_fontFamily:
988 fontFamily = a.getString(attr);
989 break;
990
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800991 case com.android.internal.R.styleable.TextView_password:
992 password = a.getBoolean(attr, password);
993 break;
994
995 case com.android.internal.R.styleable.TextView_lineSpacingExtra:
996 mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
997 break;
998
999 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
1000 mSpacingMult = a.getFloat(attr, mSpacingMult);
1001 break;
1002
1003 case com.android.internal.R.styleable.TextView_inputType:
Gilles Debunne60e21862012-01-30 15:04:14 -08001004 inputType = a.getInt(attr, EditorInfo.TYPE_NULL);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001005 break;
1006
1007 case com.android.internal.R.styleable.TextView_imeOptions:
Gilles Debunne5fae9962012-05-08 14:53:20 -07001008 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001009 mEditor.createInputContentTypeIfNeeded();
1010 mEditor.mInputContentType.imeOptions = a.getInt(attr,
1011 mEditor.mInputContentType.imeOptions);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001012 break;
1013
1014 case com.android.internal.R.styleable.TextView_imeActionLabel:
Gilles Debunne5fae9962012-05-08 14:53:20 -07001015 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001016 mEditor.createInputContentTypeIfNeeded();
1017 mEditor.mInputContentType.imeActionLabel = a.getText(attr);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001018 break;
1019
1020 case com.android.internal.R.styleable.TextView_imeActionId:
Gilles Debunne5fae9962012-05-08 14:53:20 -07001021 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001022 mEditor.createInputContentTypeIfNeeded();
1023 mEditor.mInputContentType.imeActionId = a.getInt(attr,
1024 mEditor.mInputContentType.imeActionId);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001025 break;
1026
1027 case com.android.internal.R.styleable.TextView_privateImeOptions:
1028 setPrivateImeOptions(a.getString(attr));
1029 break;
1030
1031 case com.android.internal.R.styleable.TextView_editorExtras:
1032 try {
1033 setInputExtras(a.getResourceId(attr, 0));
1034 } catch (XmlPullParserException e) {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07001035 Log.w(LOG_TAG, "Failure reading input extras", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001036 } catch (IOException e) {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07001037 Log.w(LOG_TAG, "Failure reading input extras", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001038 }
1039 break;
Adam Powellb08013c2010-09-16 16:28:11 -07001040
Gilles Debunnef75c97e2011-02-10 16:09:53 -08001041 case com.android.internal.R.styleable.TextView_textCursorDrawable:
1042 mCursorDrawableRes = a.getResourceId(attr, 0);
1043 break;
1044
Adam Powellb08013c2010-09-16 16:28:11 -07001045 case com.android.internal.R.styleable.TextView_textSelectHandleLeft:
1046 mTextSelectHandleLeftRes = a.getResourceId(attr, 0);
1047 break;
1048
1049 case com.android.internal.R.styleable.TextView_textSelectHandleRight:
1050 mTextSelectHandleRightRes = a.getResourceId(attr, 0);
1051 break;
1052
1053 case com.android.internal.R.styleable.TextView_textSelectHandle:
1054 mTextSelectHandleRes = a.getResourceId(attr, 0);
1055 break;
Gilles Debunne7b9652b2010-10-26 16:27:12 -07001056
Gilles Debunne69340442011-03-31 13:37:51 -07001057 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout:
1058 mTextEditSuggestionItemLayout = a.getResourceId(attr, 0);
1059 break;
1060
Gilles Debunne86b9c782010-11-11 10:43:48 -08001061 case com.android.internal.R.styleable.TextView_textIsSelectable:
Gilles Debunne60e21862012-01-30 15:04:14 -08001062 setTextIsSelectable(a.getBoolean(attr, false));
Gilles Debunne86b9c782010-11-11 10:43:48 -08001063 break;
Gilles Debunnef3a135b2011-05-23 16:28:47 -07001064
Adam Powell7f8f79a2011-07-07 18:35:54 -07001065 case com.android.internal.R.styleable.TextView_textAllCaps:
1066 allCaps = a.getBoolean(attr, false);
1067 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001068 }
1069 }
1070 a.recycle();
1071
1072 BufferType bufferType = BufferType.EDITABLE;
1073
Gilles Debunned7483bf2010-11-10 10:47:45 -08001074 final int variation =
1075 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
1076 final boolean passwordInputType = variation
1077 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
1078 final boolean webPasswordInputType = variation
1079 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD);
Ken Wakasa82d731a2010-12-24 23:42:41 +09001080 final boolean numberPasswordInputType = variation
1081 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
Gilles Debunned7483bf2010-11-10 10:47:45 -08001082
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001083 if (inputMethod != null) {
Gilles Debunnee15b3582010-06-16 15:17:21 -07001084 Class<?> c;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001085
1086 try {
1087 c = Class.forName(inputMethod.toString());
1088 } catch (ClassNotFoundException ex) {
1089 throw new RuntimeException(ex);
1090 }
1091
1092 try {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001093 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001094 mEditor.mKeyListener = (KeyListener) c.newInstance();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001095 } catch (InstantiationException ex) {
1096 throw new RuntimeException(ex);
1097 } catch (IllegalAccessException ex) {
1098 throw new RuntimeException(ex);
1099 }
1100 try {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001101 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001102 ? inputType
Gilles Debunne2d373a12012-04-20 15:32:19 -07001103 : mEditor.mKeyListener.getInputType();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001104 } catch (IncompatibleClassChangeError e) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001105 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001106 }
1107 } else if (digits != null) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001108 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001109 mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString());
Dianne Hackborn7ed6ee52009-09-10 18:41:28 -07001110 // If no input type was specified, we will default to generic
1111 // text, since we can't tell the IME about the set of digits
1112 // that was selected.
Gilles Debunne2d373a12012-04-20 15:32:19 -07001113 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
Dianne Hackborn7ed6ee52009-09-10 18:41:28 -07001114 ? inputType : EditorInfo.TYPE_CLASS_TEXT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001115 } else if (inputType != EditorInfo.TYPE_NULL) {
1116 setInputType(inputType, true);
Gilles Debunne91a08cf2010-11-08 17:34:49 -08001117 // If set, the input type overrides what was set using the deprecated singleLine flag.
1118 singleLine = !isMultilineInputType(inputType);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001119 } else if (phone) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001120 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001121 mEditor.mKeyListener = DialerKeyListener.getInstance();
1122 mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001123 } else if (numeric != 0) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001124 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001125 mEditor.mKeyListener = DigitsKeyListener.getInstance((numeric & SIGNED) != 0,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001126 (numeric & DECIMAL) != 0);
1127 inputType = EditorInfo.TYPE_CLASS_NUMBER;
1128 if ((numeric & SIGNED) != 0) {
1129 inputType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED;
1130 }
1131 if ((numeric & DECIMAL) != 0) {
1132 inputType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL;
1133 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07001134 mEditor.mInputType = inputType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001135 } else if (autotext || autocap != -1) {
1136 TextKeyListener.Capitalize cap;
1137
1138 inputType = EditorInfo.TYPE_CLASS_TEXT;
Gilles Debunnef95449d2010-11-05 13:54:13 -07001139
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001140 switch (autocap) {
1141 case 1:
1142 cap = TextKeyListener.Capitalize.SENTENCES;
1143 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
1144 break;
1145
1146 case 2:
1147 cap = TextKeyListener.Capitalize.WORDS;
1148 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
1149 break;
1150
1151 case 3:
1152 cap = TextKeyListener.Capitalize.CHARACTERS;
1153 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
1154 break;
1155
1156 default:
1157 cap = TextKeyListener.Capitalize.NONE;
1158 break;
1159 }
1160
Gilles Debunne5fae9962012-05-08 14:53:20 -07001161 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001162 mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap);
1163 mEditor.mInputType = inputType;
Gilles Debunne60e21862012-01-30 15:04:14 -08001164 } else if (isTextSelectable()) {
Gilles Debunne86b9c782010-11-11 10:43:48 -08001165 // Prevent text changes from keyboard.
Gilles Debunne60e21862012-01-30 15:04:14 -08001166 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001167 mEditor.mKeyListener = null;
1168 mEditor.mInputType = EditorInfo.TYPE_NULL;
Gilles Debunne60e21862012-01-30 15:04:14 -08001169 }
Gilles Debunne86b9c782010-11-11 10:43:48 -08001170 bufferType = BufferType.SPANNABLE;
Gilles Debunne86b9c782010-11-11 10:43:48 -08001171 // So that selection can be changed using arrow keys and touch is handled.
1172 setMovementMethod(ArrowKeyMovementMethod.getInstance());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001173 } else if (editable) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001174 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001175 mEditor.mKeyListener = TextKeyListener.getInstance();
1176 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001177 } else {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001178 if (mEditor != null) mEditor.mKeyListener = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001179
1180 switch (buffertype) {
1181 case 0:
1182 bufferType = BufferType.NORMAL;
1183 break;
1184 case 1:
1185 bufferType = BufferType.SPANNABLE;
1186 break;
1187 case 2:
1188 bufferType = BufferType.EDITABLE;
1189 break;
1190 }
1191 }
1192
Gilles Debunne2d373a12012-04-20 15:32:19 -07001193 if (mEditor != null) mEditor.adjustInputType(password, passwordInputType,
1194 webPasswordInputType, numberPasswordInputType);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001195
1196 if (selectallonfocus) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001197 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001198 mEditor.mSelectAllOnFocus = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001199
1200 if (bufferType == BufferType.NORMAL)
1201 bufferType = BufferType.SPANNABLE;
1202 }
1203
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07001204 // This call will save the initial left/right drawables
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001205 setCompoundDrawablesWithIntrinsicBounds(
1206 drawableLeft, drawableTop, drawableRight, drawableBottom);
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001207 setRelativeDrawablesIfNeeded(drawableStart, drawableEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001208 setCompoundDrawablePadding(drawablePadding);
1209
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08001210 // Same as setSingleLine(), but make sure the transformation method and the maximum number
Gilles Debunne066460f2010-12-15 17:31:51 -08001211 // of lines of height are unchanged for multi-line TextViews.
Gilles Debunned7483bf2010-11-10 10:47:45 -08001212 setInputTypeSingleLine(singleLine);
Gilles Debunne066460f2010-12-15 17:31:51 -08001213 applySingleLine(singleLine, singleLine, singleLine);
Gilles Debunned7483bf2010-11-10 10:47:45 -08001214
Gilles Debunne60e21862012-01-30 15:04:14 -08001215 if (singleLine && getKeyListener() == null && ellipsize < 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001216 ellipsize = 3; // END
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001217 }
1218
1219 switch (ellipsize) {
1220 case 1:
1221 setEllipsize(TextUtils.TruncateAt.START);
1222 break;
1223 case 2:
1224 setEllipsize(TextUtils.TruncateAt.MIDDLE);
1225 break;
1226 case 3:
1227 setEllipsize(TextUtils.TruncateAt.END);
1228 break;
1229 case 4:
Adam Powell282e3772011-08-30 16:51:11 -07001230 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) {
1231 setHorizontalFadingEdgeEnabled(true);
1232 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
1233 } else {
1234 setHorizontalFadingEdgeEnabled(false);
1235 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
1236 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001237 setEllipsize(TextUtils.TruncateAt.MARQUEE);
1238 break;
1239 }
1240
1241 setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000));
1242 setHintTextColor(textColorHint);
1243 setLinkTextColor(textColorLink);
1244 if (textColorHighlight != 0) {
1245 setHighlightColor(textColorHighlight);
1246 }
1247 setRawTextSize(textSize);
1248
Adam Powell7f8f79a2011-07-07 18:35:54 -07001249 if (allCaps) {
1250 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
1251 }
1252
Ken Wakasa82d731a2010-12-24 23:42:41 +09001253 if (password || passwordInputType || webPasswordInputType || numberPasswordInputType) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001254 setTransformationMethod(PasswordTransformationMethod.getInstance());
1255 typefaceIndex = MONOSPACE;
Gilles Debunne2d373a12012-04-20 15:32:19 -07001256 } else if (mEditor != null &&
1257 (mEditor.mInputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
Gilles Debunned7483bf2010-11-10 10:47:45 -08001258 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08001259 typefaceIndex = MONOSPACE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001260 }
1261
Raph Leviend570e892012-05-09 11:45:34 -07001262 setTypefaceFromAttrs(fontFamily, typefaceIndex, styleIndex);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001263
1264 if (shadowcolor != 0) {
1265 setShadowLayer(r, dx, dy, shadowcolor);
1266 }
1267
1268 if (maxlength >= 0) {
1269 setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
1270 } else {
1271 setFilters(NO_FILTERS);
1272 }
1273
1274 setText(text, bufferType);
Romain Guy4dc4f732009-06-19 15:16:40 -07001275 if (hint != null) setHint(hint);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001276
1277 /*
1278 * Views are not normally focusable unless specified to be.
1279 * However, TextViews that have input or movement methods *are*
1280 * focusable by default.
1281 */
Alan Viverette617feb92013-09-09 18:09:13 -07001282 a = context.obtainStyledAttributes(
1283 attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001284
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 }
Fabrice Di Meglio4155e2e2013-08-08 16:28:07 -07001381 resetResolvedDrawables();
1382 resolveDrawables();
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001383 }
1384 }
1385
Janos Levai042856c2010-10-15 02:53:58 +03001386 @Override
1387 public void setEnabled(boolean enabled) {
1388 if (enabled == isEnabled()) {
1389 return;
1390 }
1391
1392 if (!enabled) {
1393 // Hide the soft input if the currently active TextView is disabled
1394 InputMethodManager imm = InputMethodManager.peekInstance();
1395 if (imm != null && imm.isActive(this)) {
1396 imm.hideSoftInputFromWindow(getWindowToken(), 0);
1397 }
1398 }
Gilles Debunne545c4d42011-11-29 10:37:15 -08001399
Janos Levai042856c2010-10-15 02:53:58 +03001400 super.setEnabled(enabled);
Gilles Debunne545c4d42011-11-29 10:37:15 -08001401
Dianne Hackbornbc823852011-09-18 17:19:50 -07001402 if (enabled) {
1403 // Make sure IME is updated with current editor info.
1404 InputMethodManager imm = InputMethodManager.peekInstance();
1405 if (imm != null) imm.restartInput(this);
1406 }
Mark Wagnerf8185112011-10-25 16:33:41 -07001407
Gilles Debunne33b7de852012-03-12 11:57:48 -07001408 // Will change text color
Gilles Debunned88876a2012-03-16 17:34:04 -07001409 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001410 mEditor.invalidateTextDisplayList();
1411 mEditor.prepareCursorControllers();
Gilles Debunne545c4d42011-11-29 10:37:15 -08001412
Gilles Debunned88876a2012-03-16 17:34:04 -07001413 // start or stop the cursor blinking as appropriate
Gilles Debunne2d373a12012-04-20 15:32:19 -07001414 mEditor.makeBlink();
Gilles Debunned88876a2012-03-16 17:34:04 -07001415 }
Janos Levai042856c2010-10-15 02:53:58 +03001416 }
1417
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001418 /**
1419 * Sets the typeface and style in which the text should be displayed,
1420 * and turns on the fake bold and italic bits in the Paint if the
1421 * Typeface that you provided does not have all the bits in the
1422 * style that you specified.
1423 *
1424 * @attr ref android.R.styleable#TextView_typeface
1425 * @attr ref android.R.styleable#TextView_textStyle
1426 */
1427 public void setTypeface(Typeface tf, int style) {
1428 if (style > 0) {
1429 if (tf == null) {
1430 tf = Typeface.defaultFromStyle(style);
1431 } else {
1432 tf = Typeface.create(tf, style);
1433 }
1434
1435 setTypeface(tf);
1436 // now compute what (if any) algorithmic styling is needed
1437 int typefaceStyle = tf != null ? tf.getStyle() : 0;
1438 int need = style & ~typefaceStyle;
1439 mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
1440 mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
1441 } else {
Victoria Leaseaa0980a2012-06-11 14:46:04 -07001442 mTextPaint.setFakeBoldText(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001443 mTextPaint.setTextSkewX(0);
1444 setTypeface(tf);
1445 }
1446 }
1447
1448 /**
1449 * Subclasses override this to specify that they have a KeyListener
1450 * by default even if not specifically called for in the XML options.
1451 */
1452 protected boolean getDefaultEditable() {
1453 return false;
1454 }
1455
1456 /**
1457 * Subclasses override this to specify a default movement method.
1458 */
1459 protected MovementMethod getDefaultMovementMethod() {
1460 return null;
1461 }
1462
1463 /**
1464 * Return the text the TextView is displaying. If setText() was called with
1465 * an argument of BufferType.SPANNABLE or BufferType.EDITABLE, you can cast
1466 * the return value from this method to Spannable or Editable, respectively.
1467 *
1468 * Note: The content of the return value should not be modified. If you want
1469 * a modifiable one, you should make your own copy first.
Gilles Debunnef03acef2012-04-30 19:26:19 -07001470 *
1471 * @attr ref android.R.styleable#TextView_text
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001472 */
1473 @ViewDebug.CapturedViewProperty
1474 public CharSequence getText() {
1475 return mText;
1476 }
1477
1478 /**
1479 * Returns the length, in characters, of the text managed by this TextView
1480 */
1481 public int length() {
1482 return mText.length();
1483 }
1484
1485 /**
1486 * Return the text the TextView is displaying as an Editable object. If
1487 * the text is not editable, null is returned.
1488 *
1489 * @see #getText
1490 */
1491 public Editable getEditableText() {
1492 return (mText instanceof Editable) ? (Editable)mText : null;
1493 }
1494
1495 /**
1496 * @return the height of one standard line in pixels. Note that markup
1497 * within the text can cause individual lines to be taller or shorter
1498 * than this height, and the layout may contain additional first-
1499 * or last-line padding.
1500 */
1501 public int getLineHeight() {
Gilles Debunne96e6b8b2010-12-14 13:43:45 -08001502 return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001503 }
1504
1505 /**
1506 * @return the Layout that is currently being used to display the text.
1507 * This can be null if the text or width has recently changes.
1508 */
1509 public final Layout getLayout() {
1510 return mLayout;
1511 }
1512
1513 /**
Fabrice Di Meglio0ed59fa2012-05-29 20:32:51 -07001514 * @return the Layout that is currently being used to display the hint text.
1515 * This can be null.
1516 */
1517 final Layout getHintLayout() {
1518 return mHintLayout;
1519 }
1520
1521 /**
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07001522 * Retrieve the {@link android.content.UndoManager} that is currently associated
1523 * with this TextView. By default there is no associated UndoManager, so null
1524 * is returned. One can be associated with the TextView through
1525 * {@link #setUndoManager(android.content.UndoManager, String)}
Dianne Hackbornb811e642013-09-04 17:43:56 -07001526 *
1527 * @hide
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07001528 */
1529 public final UndoManager getUndoManager() {
1530 return mEditor == null ? null : mEditor.mUndoManager;
1531 }
1532
1533 /**
1534 * Associate an {@link android.content.UndoManager} with this TextView. Once
1535 * done, all edit operations on the TextView will result in appropriate
1536 * {@link android.content.UndoOperation} objects pushed on the given UndoManager's
1537 * stack.
1538 *
1539 * @param undoManager The {@link android.content.UndoManager} to associate with
1540 * this TextView, or null to clear any existing association.
1541 * @param tag String tag identifying this particular TextView owner in the
1542 * UndoManager. This is used to keep the correct association with the
1543 * {@link android.content.UndoOwner} of any operations inside of the UndoManager.
Dianne Hackbornb811e642013-09-04 17:43:56 -07001544 *
1545 * @hide
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07001546 */
1547 public final void setUndoManager(UndoManager undoManager, String tag) {
1548 if (undoManager != null) {
1549 createEditorIfNeeded();
1550 mEditor.mUndoManager = undoManager;
1551 mEditor.mUndoOwner = undoManager.getOwner(tag, this);
1552 mEditor.mUndoInputFilter = new Editor.UndoInputFilter(mEditor);
1553 if (!(mText instanceof Editable)) {
1554 setText(mText, BufferType.EDITABLE);
1555 }
1556
1557 setFilters((Editable) mText, mFilters);
1558 } else if (mEditor != null) {
1559 // XXX need to destroy all associated state.
1560 mEditor.mUndoManager = null;
1561 mEditor.mUndoOwner = null;
1562 mEditor.mUndoInputFilter = null;
1563 }
1564 }
1565
1566 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001567 * @return the current key listener for this TextView.
1568 * This will frequently be null for non-EditText TextViews.
Gilles Debunnef03acef2012-04-30 19:26:19 -07001569 *
1570 * @attr ref android.R.styleable#TextView_numeric
1571 * @attr ref android.R.styleable#TextView_digits
1572 * @attr ref android.R.styleable#TextView_phoneNumber
1573 * @attr ref android.R.styleable#TextView_inputMethod
1574 * @attr ref android.R.styleable#TextView_capitalize
1575 * @attr ref android.R.styleable#TextView_autoText
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001576 */
1577 public final KeyListener getKeyListener() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001578 return mEditor == null ? null : mEditor.mKeyListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001579 }
1580
1581 /**
1582 * Sets the key listener to be used with this TextView. This can be null
1583 * to disallow user input. Note that this method has significant and
1584 * subtle interactions with soft keyboards and other input method:
1585 * see {@link KeyListener#getInputType() KeyListener.getContentType()}
1586 * for important details. Calling this method will replace the current
1587 * content type of the text view with the content type returned by the
1588 * key listener.
1589 * <p>
1590 * Be warned that if you want a TextView with a key listener or movement
1591 * method not to be focusable, or if you want a TextView without a
1592 * key listener or movement method to be focusable, you must call
1593 * {@link #setFocusable} again after calling this to get the focusability
1594 * back the way you want it.
1595 *
1596 * @attr ref android.R.styleable#TextView_numeric
1597 * @attr ref android.R.styleable#TextView_digits
1598 * @attr ref android.R.styleable#TextView_phoneNumber
1599 * @attr ref android.R.styleable#TextView_inputMethod
1600 * @attr ref android.R.styleable#TextView_capitalize
1601 * @attr ref android.R.styleable#TextView_autoText
1602 */
1603 public void setKeyListener(KeyListener input) {
1604 setKeyListenerOnly(input);
1605 fixFocusableAndClickableSettings();
1606
1607 if (input != null) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001608 createEditorIfNeeded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001609 try {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001610 mEditor.mInputType = mEditor.mKeyListener.getInputType();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001611 } catch (IncompatibleClassChangeError e) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001612 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001613 }
Gilles Debunned7483bf2010-11-10 10:47:45 -08001614 // Change inputType, without affecting transformation.
1615 // No need to applySingleLine since mSingleLine is unchanged.
1616 setInputTypeSingleLine(mSingleLine);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001617 } else {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001618 if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001619 }
1620
1621 InputMethodManager imm = InputMethodManager.peekInstance();
1622 if (imm != null) imm.restartInput(this);
1623 }
1624
1625 private void setKeyListenerOnly(KeyListener input) {
Gilles Debunne60e21862012-01-30 15:04:14 -08001626 if (mEditor == null && input == null) return; // null is the default value
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001627
Gilles Debunne5fae9962012-05-08 14:53:20 -07001628 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001629 if (mEditor.mKeyListener != input) {
1630 mEditor.mKeyListener = input;
Gilles Debunne60e21862012-01-30 15:04:14 -08001631 if (input != null && !(mText instanceof Editable)) {
1632 setText(mText);
1633 }
1634
1635 setFilters((Editable) mText, mFilters);
1636 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001637 }
1638
1639 /**
1640 * @return the movement method being used for this TextView.
1641 * This will frequently be null for non-EditText TextViews.
1642 */
1643 public final MovementMethod getMovementMethod() {
1644 return mMovement;
1645 }
1646
1647 /**
1648 * Sets the movement method (arrow key handler) to be used for
1649 * this TextView. This can be null to disallow using the arrow keys
1650 * to move the cursor or scroll the view.
1651 * <p>
1652 * Be warned that if you want a TextView with a key listener or movement
1653 * method not to be focusable, or if you want a TextView without a
1654 * key listener or movement method to be focusable, you must call
1655 * {@link #setFocusable} again after calling this to get the focusability
1656 * back the way you want it.
1657 */
1658 public final void setMovementMethod(MovementMethod movement) {
Gilles Debunne60e21862012-01-30 15:04:14 -08001659 if (mMovement != movement) {
1660 mMovement = movement;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001661
Gilles Debunne60e21862012-01-30 15:04:14 -08001662 if (movement != null && !(mText instanceof Spannable)) {
1663 setText(mText);
1664 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001665
Gilles Debunne60e21862012-01-30 15:04:14 -08001666 fixFocusableAndClickableSettings();
Gilles Debunnef788a9f2010-07-22 10:17:23 -07001667
Gilles Debunne2d373a12012-04-20 15:32:19 -07001668 // SelectionModifierCursorController depends on textCanBeSelected, which depends on
1669 // mMovement
1670 if (mEditor != null) mEditor.prepareCursorControllers();
Gilles Debunne60e21862012-01-30 15:04:14 -08001671 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001672 }
1673
1674 private void fixFocusableAndClickableSettings() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001675 if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001676 setFocusable(true);
1677 setClickable(true);
1678 setLongClickable(true);
1679 } else {
1680 setFocusable(false);
1681 setClickable(false);
1682 setLongClickable(false);
1683 }
1684 }
1685
1686 /**
1687 * @return the current transformation method for this TextView.
1688 * This will frequently be null except for single-line and password
1689 * fields.
Gilles Debunnef03acef2012-04-30 19:26:19 -07001690 *
1691 * @attr ref android.R.styleable#TextView_password
1692 * @attr ref android.R.styleable#TextView_singleLine
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001693 */
1694 public final TransformationMethod getTransformationMethod() {
1695 return mTransformation;
1696 }
1697
1698 /**
1699 * Sets the transformation that is applied to the text that this
1700 * TextView is displaying.
1701 *
1702 * @attr ref android.R.styleable#TextView_password
1703 * @attr ref android.R.styleable#TextView_singleLine
1704 */
1705 public final void setTransformationMethod(TransformationMethod method) {
1706 if (method == mTransformation) {
1707 // Avoid the setText() below if the transformation is
1708 // the same.
1709 return;
1710 }
1711 if (mTransformation != null) {
1712 if (mText instanceof Spannable) {
1713 ((Spannable) mText).removeSpan(mTransformation);
1714 }
1715 }
1716
1717 mTransformation = method;
1718
Adam Powell7f8f79a2011-07-07 18:35:54 -07001719 if (method instanceof TransformationMethod2) {
1720 TransformationMethod2 method2 = (TransformationMethod2) method;
Gilles Debunne60e21862012-01-30 15:04:14 -08001721 mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable);
Adam Powell7f8f79a2011-07-07 18:35:54 -07001722 method2.setLengthChangesAllowed(mAllowTransformationLengthChange);
1723 } else {
1724 mAllowTransformationLengthChange = false;
1725 }
1726
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001727 setText(mText);
Svetoslav Ganovc406be92012-05-11 16:12:32 -07001728
1729 if (hasPasswordTransformationMethod()) {
Alan Viverette77e9a282013-09-12 17:16:09 -07001730 notifyViewAccessibilityStateChangedIfNeeded(
1731 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
Svetoslav Ganovc406be92012-05-11 16:12:32 -07001732 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001733 }
1734
1735 /**
1736 * Returns the top padding of the view, plus space for the top
1737 * Drawable if any.
1738 */
1739 public int getCompoundPaddingTop() {
1740 final Drawables dr = mDrawables;
1741 if (dr == null || dr.mDrawableTop == null) {
1742 return mPaddingTop;
1743 } else {
1744 return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
1745 }
1746 }
1747
1748 /**
1749 * Returns the bottom padding of the view, plus space for the bottom
1750 * Drawable if any.
1751 */
1752 public int getCompoundPaddingBottom() {
1753 final Drawables dr = mDrawables;
1754 if (dr == null || dr.mDrawableBottom == null) {
1755 return mPaddingBottom;
1756 } else {
1757 return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
1758 }
1759 }
1760
1761 /**
1762 * Returns the left padding of the view, plus space for the left
1763 * Drawable if any.
1764 */
1765 public int getCompoundPaddingLeft() {
1766 final Drawables dr = mDrawables;
1767 if (dr == null || dr.mDrawableLeft == null) {
1768 return mPaddingLeft;
1769 } else {
1770 return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
1771 }
1772 }
1773
1774 /**
1775 * Returns the right padding of the view, plus space for the right
1776 * Drawable if any.
1777 */
1778 public int getCompoundPaddingRight() {
1779 final Drawables dr = mDrawables;
1780 if (dr == null || dr.mDrawableRight == null) {
1781 return mPaddingRight;
1782 } else {
1783 return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
1784 }
1785 }
1786
1787 /**
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001788 * Returns the start padding of the view, plus space for the start
1789 * Drawable if any.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001790 */
1791 public int getCompoundPaddingStart() {
1792 resolveDrawables();
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07001793 switch(getLayoutDirection()) {
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001794 default:
1795 case LAYOUT_DIRECTION_LTR:
1796 return getCompoundPaddingLeft();
1797 case LAYOUT_DIRECTION_RTL:
1798 return getCompoundPaddingRight();
1799 }
1800 }
1801
1802 /**
1803 * Returns the end padding of the view, plus space for the end
1804 * Drawable if any.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001805 */
1806 public int getCompoundPaddingEnd() {
1807 resolveDrawables();
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07001808 switch(getLayoutDirection()) {
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001809 default:
1810 case LAYOUT_DIRECTION_LTR:
1811 return getCompoundPaddingRight();
1812 case LAYOUT_DIRECTION_RTL:
1813 return getCompoundPaddingLeft();
1814 }
1815 }
1816
1817 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001818 * Returns the extended top padding of the view, including both the
1819 * top Drawable if any and any extra space to keep more than maxLines
1820 * of text from showing. It is only valid to call this after measuring.
1821 */
1822 public int getExtendedPaddingTop() {
1823 if (mMaxMode != LINES) {
1824 return getCompoundPaddingTop();
1825 }
1826
1827 if (mLayout.getLineCount() <= mMaximum) {
1828 return getCompoundPaddingTop();
1829 }
1830
1831 int top = getCompoundPaddingTop();
1832 int bottom = getCompoundPaddingBottom();
1833 int viewht = getHeight() - top - bottom;
1834 int layoutht = mLayout.getLineTop(mMaximum);
1835
1836 if (layoutht >= viewht) {
1837 return top;
1838 }
1839
1840 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
1841 if (gravity == Gravity.TOP) {
1842 return top;
1843 } else if (gravity == Gravity.BOTTOM) {
1844 return top + viewht - layoutht;
1845 } else { // (gravity == Gravity.CENTER_VERTICAL)
1846 return top + (viewht - layoutht) / 2;
1847 }
1848 }
1849
1850 /**
1851 * Returns the extended bottom padding of the view, including both the
1852 * bottom Drawable if any and any extra space to keep more than maxLines
1853 * of text from showing. It is only valid to call this after measuring.
1854 */
1855 public int getExtendedPaddingBottom() {
1856 if (mMaxMode != LINES) {
1857 return getCompoundPaddingBottom();
1858 }
1859
1860 if (mLayout.getLineCount() <= mMaximum) {
1861 return getCompoundPaddingBottom();
1862 }
1863
1864 int top = getCompoundPaddingTop();
1865 int bottom = getCompoundPaddingBottom();
1866 int viewht = getHeight() - top - bottom;
1867 int layoutht = mLayout.getLineTop(mMaximum);
1868
1869 if (layoutht >= viewht) {
1870 return bottom;
1871 }
1872
1873 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
1874 if (gravity == Gravity.TOP) {
1875 return bottom + viewht - layoutht;
1876 } else if (gravity == Gravity.BOTTOM) {
1877 return bottom;
1878 } else { // (gravity == Gravity.CENTER_VERTICAL)
1879 return bottom + (viewht - layoutht) / 2;
1880 }
1881 }
1882
1883 /**
1884 * Returns the total left padding of the view, including the left
1885 * Drawable if any.
1886 */
1887 public int getTotalPaddingLeft() {
1888 return getCompoundPaddingLeft();
1889 }
1890
1891 /**
1892 * Returns the total right padding of the view, including the right
1893 * Drawable if any.
1894 */
1895 public int getTotalPaddingRight() {
1896 return getCompoundPaddingRight();
1897 }
1898
1899 /**
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001900 * Returns the total start padding of the view, including the start
1901 * Drawable if any.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001902 */
1903 public int getTotalPaddingStart() {
1904 return getCompoundPaddingStart();
1905 }
1906
1907 /**
1908 * Returns the total end padding of the view, including the end
1909 * Drawable if any.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001910 */
1911 public int getTotalPaddingEnd() {
1912 return getCompoundPaddingEnd();
1913 }
1914
1915 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001916 * Returns the total top padding of the view, including the top
1917 * Drawable if any, the extra space to keep more than maxLines
1918 * from showing, and the vertical offset for gravity, if any.
1919 */
1920 public int getTotalPaddingTop() {
1921 return getExtendedPaddingTop() + getVerticalOffset(true);
1922 }
1923
1924 /**
1925 * Returns the total bottom padding of the view, including the bottom
1926 * Drawable if any, the extra space to keep more than maxLines
1927 * from showing, and the vertical offset for gravity, if any.
1928 */
1929 public int getTotalPaddingBottom() {
1930 return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
1931 }
1932
1933 /**
1934 * Sets the Drawables (if any) to appear to the left of, above,
1935 * to the right of, and below the text. Use null if you do not
1936 * want a Drawable there. The Drawables must already have had
1937 * {@link Drawable#setBounds} called.
1938 *
1939 * @attr ref android.R.styleable#TextView_drawableLeft
1940 * @attr ref android.R.styleable#TextView_drawableTop
1941 * @attr ref android.R.styleable#TextView_drawableRight
1942 * @attr ref android.R.styleable#TextView_drawableBottom
1943 */
1944 public void setCompoundDrawables(Drawable left, Drawable top,
1945 Drawable right, Drawable bottom) {
1946 Drawables dr = mDrawables;
1947
1948 final boolean drawables = left != null || top != null
1949 || right != null || bottom != null;
1950
1951 if (!drawables) {
1952 // Clearing drawables... can we free the data structure?
1953 if (dr != null) {
1954 if (dr.mDrawablePadding == 0) {
1955 mDrawables = null;
1956 } else {
1957 // We need to retain the last set padding, so just clear
1958 // out all of the fields in the existing structure.
Romain Guy48540eb2009-05-19 16:44:57 -07001959 if (dr.mDrawableLeft != null) dr.mDrawableLeft.setCallback(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001960 dr.mDrawableLeft = null;
Romain Guy48540eb2009-05-19 16:44:57 -07001961 if (dr.mDrawableTop != null) dr.mDrawableTop.setCallback(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001962 dr.mDrawableTop = null;
Romain Guy48540eb2009-05-19 16:44:57 -07001963 if (dr.mDrawableRight != null) dr.mDrawableRight.setCallback(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001964 dr.mDrawableRight = null;
Romain Guy48540eb2009-05-19 16:44:57 -07001965 if (dr.mDrawableBottom != null) dr.mDrawableBottom.setCallback(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001966 dr.mDrawableBottom = null;
1967 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
1968 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
1969 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
1970 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
1971 }
1972 }
1973 } else {
1974 if (dr == null) {
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07001975 mDrawables = dr = new Drawables(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001976 }
1977
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07001978 mDrawables.mOverride = false;
1979
Romain Guy48540eb2009-05-19 16:44:57 -07001980 if (dr.mDrawableLeft != left && dr.mDrawableLeft != null) {
1981 dr.mDrawableLeft.setCallback(null);
1982 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001983 dr.mDrawableLeft = left;
Romain Guy8e618e52010-03-08 12:18:20 -08001984
1985 if (dr.mDrawableTop != top && dr.mDrawableTop != null) {
Romain Guy48540eb2009-05-19 16:44:57 -07001986 dr.mDrawableTop.setCallback(null);
1987 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001988 dr.mDrawableTop = top;
Romain Guy8e618e52010-03-08 12:18:20 -08001989
1990 if (dr.mDrawableRight != right && dr.mDrawableRight != null) {
Romain Guy48540eb2009-05-19 16:44:57 -07001991 dr.mDrawableRight.setCallback(null);
1992 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001993 dr.mDrawableRight = right;
Romain Guy8e618e52010-03-08 12:18:20 -08001994
1995 if (dr.mDrawableBottom != bottom && dr.mDrawableBottom != null) {
Romain Guy48540eb2009-05-19 16:44:57 -07001996 dr.mDrawableBottom.setCallback(null);
1997 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001998 dr.mDrawableBottom = bottom;
1999
2000 final Rect compoundRect = dr.mCompoundRect;
Romain Guy48540eb2009-05-19 16:44:57 -07002001 int[] state;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002002
2003 state = getDrawableState();
2004
2005 if (left != null) {
2006 left.setState(state);
2007 left.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07002008 left.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002009 dr.mDrawableSizeLeft = compoundRect.width();
2010 dr.mDrawableHeightLeft = compoundRect.height();
2011 } else {
2012 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2013 }
2014
2015 if (right != null) {
2016 right.setState(state);
2017 right.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07002018 right.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002019 dr.mDrawableSizeRight = compoundRect.width();
2020 dr.mDrawableHeightRight = compoundRect.height();
2021 } else {
2022 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2023 }
2024
2025 if (top != null) {
2026 top.setState(state);
2027 top.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07002028 top.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002029 dr.mDrawableSizeTop = compoundRect.height();
2030 dr.mDrawableWidthTop = compoundRect.width();
2031 } else {
2032 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2033 }
2034
2035 if (bottom != null) {
2036 bottom.setState(state);
2037 bottom.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07002038 bottom.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002039 dr.mDrawableSizeBottom = compoundRect.height();
2040 dr.mDrawableWidthBottom = compoundRect.width();
2041 } else {
2042 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2043 }
2044 }
2045
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002046 // Save initial left/right drawables
2047 if (dr != null) {
2048 dr.mDrawableLeftInitial = left;
2049 dr.mDrawableRightInitial = right;
2050 }
2051
Fabrice Di Meglio3f5a90b2013-06-24 19:22:25 -07002052 resetResolvedDrawables();
2053 resolveDrawables();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002054 invalidate();
2055 requestLayout();
2056 }
2057
2058 /**
2059 * Sets the Drawables (if any) to appear to the left of, above,
2060 * to the right of, and below the text. Use 0 if you do not
2061 * want a Drawable there. The Drawables' bounds will be set to
2062 * their intrinsic bounds.
2063 *
2064 * @param left Resource identifier of the left Drawable.
2065 * @param top Resource identifier of the top Drawable.
2066 * @param right Resource identifier of the right Drawable.
2067 * @param bottom Resource identifier of the bottom Drawable.
2068 *
2069 * @attr ref android.R.styleable#TextView_drawableLeft
2070 * @attr ref android.R.styleable#TextView_drawableTop
2071 * @attr ref android.R.styleable#TextView_drawableRight
2072 * @attr ref android.R.styleable#TextView_drawableBottom
2073 */
Daniel Sandler820ba322012-03-23 16:36:00 -05002074 @android.view.RemotableViewMethod
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002075 public void setCompoundDrawablesWithIntrinsicBounds(int left, int top, int right, int bottom) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -08002076 final Context context = getContext();
2077 setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null,
2078 top != 0 ? context.getDrawable(top) : null,
2079 right != 0 ? context.getDrawable(right) : null,
2080 bottom != 0 ? context.getDrawable(bottom) : null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002081 }
2082
2083 /**
2084 * Sets the Drawables (if any) to appear to the left of, above,
2085 * to the right of, and below the text. Use null if you do not
2086 * want a Drawable there. The Drawables' bounds will be set to
2087 * their intrinsic bounds.
2088 *
2089 * @attr ref android.R.styleable#TextView_drawableLeft
2090 * @attr ref android.R.styleable#TextView_drawableTop
2091 * @attr ref android.R.styleable#TextView_drawableRight
2092 * @attr ref android.R.styleable#TextView_drawableBottom
2093 */
2094 public void setCompoundDrawablesWithIntrinsicBounds(Drawable left, Drawable top,
2095 Drawable right, Drawable bottom) {
2096
2097 if (left != null) {
2098 left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
2099 }
2100 if (right != null) {
2101 right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
2102 }
2103 if (top != null) {
2104 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
2105 }
2106 if (bottom != null) {
2107 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
2108 }
2109 setCompoundDrawables(left, top, right, bottom);
2110 }
2111
2112 /**
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002113 * Sets the Drawables (if any) to appear to the start of, above,
2114 * to the end of, and below the text. Use null if you do not
2115 * want a Drawable there. The Drawables must already have had
2116 * {@link Drawable#setBounds} called.
2117 *
2118 * @attr ref android.R.styleable#TextView_drawableStart
2119 * @attr ref android.R.styleable#TextView_drawableTop
2120 * @attr ref android.R.styleable#TextView_drawableEnd
2121 * @attr ref android.R.styleable#TextView_drawableBottom
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002122 */
2123 public void setCompoundDrawablesRelative(Drawable start, Drawable top,
2124 Drawable end, Drawable bottom) {
2125 Drawables dr = mDrawables;
2126
2127 final boolean drawables = start != null || top != null
2128 || end != null || bottom != null;
2129
2130 if (!drawables) {
2131 // Clearing drawables... can we free the data structure?
2132 if (dr != null) {
2133 if (dr.mDrawablePadding == 0) {
2134 mDrawables = null;
2135 } else {
2136 // We need to retain the last set padding, so just clear
2137 // out all of the fields in the existing structure.
2138 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
2139 dr.mDrawableStart = null;
2140 if (dr.mDrawableTop != null) dr.mDrawableTop.setCallback(null);
2141 dr.mDrawableTop = null;
2142 if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null);
2143 dr.mDrawableEnd = null;
2144 if (dr.mDrawableBottom != null) dr.mDrawableBottom.setCallback(null);
2145 dr.mDrawableBottom = null;
2146 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2147 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2148 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2149 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2150 }
2151 }
2152 } else {
2153 if (dr == null) {
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002154 mDrawables = dr = new Drawables(getContext());
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002155 }
2156
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002157 mDrawables.mOverride = true;
2158
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002159 if (dr.mDrawableStart != start && dr.mDrawableStart != null) {
2160 dr.mDrawableStart.setCallback(null);
2161 }
2162 dr.mDrawableStart = start;
2163
2164 if (dr.mDrawableTop != top && dr.mDrawableTop != null) {
2165 dr.mDrawableTop.setCallback(null);
2166 }
2167 dr.mDrawableTop = top;
2168
2169 if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) {
2170 dr.mDrawableEnd.setCallback(null);
2171 }
2172 dr.mDrawableEnd = end;
2173
2174 if (dr.mDrawableBottom != bottom && dr.mDrawableBottom != null) {
2175 dr.mDrawableBottom.setCallback(null);
2176 }
2177 dr.mDrawableBottom = bottom;
2178
2179 final Rect compoundRect = dr.mCompoundRect;
2180 int[] state;
2181
2182 state = getDrawableState();
2183
2184 if (start != null) {
2185 start.setState(state);
2186 start.copyBounds(compoundRect);
2187 start.setCallback(this);
2188 dr.mDrawableSizeStart = compoundRect.width();
2189 dr.mDrawableHeightStart = compoundRect.height();
2190 } else {
2191 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2192 }
2193
2194 if (end != null) {
2195 end.setState(state);
2196 end.copyBounds(compoundRect);
2197 end.setCallback(this);
2198 dr.mDrawableSizeEnd = compoundRect.width();
2199 dr.mDrawableHeightEnd = compoundRect.height();
2200 } else {
2201 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2202 }
2203
2204 if (top != null) {
2205 top.setState(state);
2206 top.copyBounds(compoundRect);
2207 top.setCallback(this);
2208 dr.mDrawableSizeTop = compoundRect.height();
2209 dr.mDrawableWidthTop = compoundRect.width();
2210 } else {
2211 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2212 }
2213
2214 if (bottom != null) {
2215 bottom.setState(state);
2216 bottom.copyBounds(compoundRect);
2217 bottom.setCallback(this);
2218 dr.mDrawableSizeBottom = compoundRect.height();
2219 dr.mDrawableWidthBottom = compoundRect.width();
2220 } else {
2221 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2222 }
2223 }
2224
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002225 resetResolvedDrawables();
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002226 resolveDrawables();
2227 invalidate();
2228 requestLayout();
2229 }
2230
2231 /**
2232 * Sets the Drawables (if any) to appear to the start of, above,
2233 * to the end of, and below the text. Use 0 if you do not
2234 * want a Drawable there. The Drawables' bounds will be set to
2235 * their intrinsic bounds.
2236 *
2237 * @param start Resource identifier of the start Drawable.
2238 * @param top Resource identifier of the top Drawable.
2239 * @param end Resource identifier of the end Drawable.
2240 * @param bottom Resource identifier of the bottom Drawable.
2241 *
2242 * @attr ref android.R.styleable#TextView_drawableStart
2243 * @attr ref android.R.styleable#TextView_drawableTop
2244 * @attr ref android.R.styleable#TextView_drawableEnd
2245 * @attr ref android.R.styleable#TextView_drawableBottom
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002246 */
Daniel Sandler820ba322012-03-23 16:36:00 -05002247 @android.view.RemotableViewMethod
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002248 public void setCompoundDrawablesRelativeWithIntrinsicBounds(int start, int top, int end,
2249 int bottom) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -08002250 final Context context = getContext();
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002251 setCompoundDrawablesRelativeWithIntrinsicBounds(
Alan Viverette8eea3ea2014-02-03 18:40:20 -08002252 start != 0 ? context.getDrawable(start) : null,
2253 top != 0 ? context.getDrawable(top) : null,
2254 end != 0 ? context.getDrawable(end) : null,
2255 bottom != 0 ? context.getDrawable(bottom) : null);
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002256 }
2257
2258 /**
2259 * Sets the Drawables (if any) to appear to the start of, above,
2260 * to the end of, and below the text. Use null if you do not
2261 * want a Drawable there. The Drawables' bounds will be set to
2262 * their intrinsic bounds.
2263 *
2264 * @attr ref android.R.styleable#TextView_drawableStart
2265 * @attr ref android.R.styleable#TextView_drawableTop
2266 * @attr ref android.R.styleable#TextView_drawableEnd
2267 * @attr ref android.R.styleable#TextView_drawableBottom
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002268 */
2269 public void setCompoundDrawablesRelativeWithIntrinsicBounds(Drawable start, Drawable top,
2270 Drawable end, Drawable bottom) {
2271
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002272 if (start != null) {
2273 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
2274 }
2275 if (end != null) {
2276 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
2277 }
2278 if (top != null) {
2279 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
2280 }
2281 if (bottom != null) {
2282 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
2283 }
2284 setCompoundDrawablesRelative(start, top, end, bottom);
2285 }
2286
2287 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002288 * Returns drawables for the left, top, right, and bottom borders.
Gilles Debunnef03acef2012-04-30 19:26:19 -07002289 *
2290 * @attr ref android.R.styleable#TextView_drawableLeft
2291 * @attr ref android.R.styleable#TextView_drawableTop
2292 * @attr ref android.R.styleable#TextView_drawableRight
2293 * @attr ref android.R.styleable#TextView_drawableBottom
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002294 */
2295 public Drawable[] getCompoundDrawables() {
2296 final Drawables dr = mDrawables;
2297 if (dr != null) {
2298 return new Drawable[] {
2299 dr.mDrawableLeft, dr.mDrawableTop, dr.mDrawableRight, dr.mDrawableBottom
2300 };
2301 } else {
2302 return new Drawable[] { null, null, null, null };
2303 }
2304 }
2305
2306 /**
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002307 * Returns drawables for the start, top, end, and bottom borders.
Gilles Debunnef03acef2012-04-30 19:26:19 -07002308 *
2309 * @attr ref android.R.styleable#TextView_drawableStart
2310 * @attr ref android.R.styleable#TextView_drawableTop
2311 * @attr ref android.R.styleable#TextView_drawableEnd
2312 * @attr ref android.R.styleable#TextView_drawableBottom
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002313 */
2314 public Drawable[] getCompoundDrawablesRelative() {
2315 final Drawables dr = mDrawables;
2316 if (dr != null) {
2317 return new Drawable[] {
2318 dr.mDrawableStart, dr.mDrawableTop, dr.mDrawableEnd, dr.mDrawableBottom
2319 };
2320 } else {
2321 return new Drawable[] { null, null, null, null };
2322 }
2323 }
2324
2325 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002326 * Sets the size of the padding between the compound drawables and
2327 * the text.
2328 *
2329 * @attr ref android.R.styleable#TextView_drawablePadding
2330 */
Daniel Sandler820ba322012-03-23 16:36:00 -05002331 @android.view.RemotableViewMethod
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002332 public void setCompoundDrawablePadding(int pad) {
2333 Drawables dr = mDrawables;
2334 if (pad == 0) {
2335 if (dr != null) {
2336 dr.mDrawablePadding = pad;
2337 }
2338 } else {
2339 if (dr == null) {
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002340 mDrawables = dr = new Drawables(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002341 }
2342 dr.mDrawablePadding = pad;
2343 }
2344
2345 invalidate();
2346 requestLayout();
2347 }
2348
2349 /**
2350 * Returns the padding between the compound drawables and the text.
Gilles Debunnef03acef2012-04-30 19:26:19 -07002351 *
2352 * @attr ref android.R.styleable#TextView_drawablePadding
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002353 */
2354 public int getCompoundDrawablePadding() {
2355 final Drawables dr = mDrawables;
2356 return dr != null ? dr.mDrawablePadding : 0;
2357 }
2358
2359 @Override
2360 public void setPadding(int left, int top, int right, int bottom) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07002361 if (left != mPaddingLeft ||
2362 right != mPaddingRight ||
2363 top != mPaddingTop ||
2364 bottom != mPaddingBottom) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002365 nullLayouts();
2366 }
2367
2368 // the super call will requestLayout()
2369 super.setPadding(left, top, right, bottom);
2370 invalidate();
2371 }
2372
Fabrice Di Megliobf923eb2012-03-07 16:20:22 -08002373 @Override
2374 public void setPaddingRelative(int start, int top, int end, int bottom) {
2375 if (start != getPaddingStart() ||
2376 end != getPaddingEnd() ||
2377 top != mPaddingTop ||
2378 bottom != mPaddingBottom) {
2379 nullLayouts();
2380 }
2381
2382 // the super call will requestLayout()
2383 super.setPaddingRelative(start, top, end, bottom);
2384 invalidate();
2385 }
2386
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002387 /**
2388 * Gets the autolink mask of the text. See {@link
2389 * android.text.util.Linkify#ALL Linkify.ALL} and peers for
2390 * possible values.
2391 *
2392 * @attr ref android.R.styleable#TextView_autoLink
2393 */
2394 public final int getAutoLinkMask() {
2395 return mAutoLinkMask;
2396 }
2397
2398 /**
2399 * Sets the text color, size, style, hint color, and highlight color
2400 * from the specified TextAppearance resource.
2401 */
2402 public void setTextAppearance(Context context, int resid) {
2403 TypedArray appearance =
2404 context.obtainStyledAttributes(resid,
2405 com.android.internal.R.styleable.TextAppearance);
2406
2407 int color;
2408 ColorStateList colors;
2409 int ts;
2410
Gilles Debunne2d373a12012-04-20 15:32:19 -07002411 color = appearance.getColor(
2412 com.android.internal.R.styleable.TextAppearance_textColorHighlight, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002413 if (color != 0) {
2414 setHighlightColor(color);
2415 }
2416
2417 colors = appearance.getColorStateList(com.android.internal.R.styleable.
2418 TextAppearance_textColor);
2419 if (colors != null) {
2420 setTextColor(colors);
2421 }
2422
2423 ts = appearance.getDimensionPixelSize(com.android.internal.R.styleable.
2424 TextAppearance_textSize, 0);
2425 if (ts != 0) {
2426 setRawTextSize(ts);
2427 }
2428
2429 colors = appearance.getColorStateList(com.android.internal.R.styleable.
2430 TextAppearance_textColorHint);
2431 if (colors != null) {
2432 setHintTextColor(colors);
2433 }
2434
2435 colors = appearance.getColorStateList(com.android.internal.R.styleable.
2436 TextAppearance_textColorLink);
2437 if (colors != null) {
2438 setLinkTextColor(colors);
2439 }
2440
Raph Leviend570e892012-05-09 11:45:34 -07002441 String familyName;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002442 int typefaceIndex, styleIndex;
2443
Raph Leviend570e892012-05-09 11:45:34 -07002444 familyName = appearance.getString(com.android.internal.R.styleable.
2445 TextAppearance_fontFamily);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002446 typefaceIndex = appearance.getInt(com.android.internal.R.styleable.
2447 TextAppearance_typeface, -1);
2448 styleIndex = appearance.getInt(com.android.internal.R.styleable.
2449 TextAppearance_textStyle, -1);
2450
Raph Leviend570e892012-05-09 11:45:34 -07002451 setTypefaceFromAttrs(familyName, typefaceIndex, styleIndex);
Gilles Debunne21078e42011-08-02 10:22:35 -07002452
Adam Powellac91df82013-02-14 13:48:47 -08002453 final int shadowcolor = appearance.getInt(
2454 com.android.internal.R.styleable.TextAppearance_shadowColor, 0);
2455 if (shadowcolor != 0) {
2456 final float dx = appearance.getFloat(
2457 com.android.internal.R.styleable.TextAppearance_shadowDx, 0);
2458 final float dy = appearance.getFloat(
2459 com.android.internal.R.styleable.TextAppearance_shadowDy, 0);
2460 final float r = appearance.getFloat(
2461 com.android.internal.R.styleable.TextAppearance_shadowRadius, 0);
2462
2463 setShadowLayer(r, dx, dy, shadowcolor);
2464 }
2465
Adam Powell7f8f79a2011-07-07 18:35:54 -07002466 if (appearance.getBoolean(com.android.internal.R.styleable.TextAppearance_textAllCaps,
2467 false)) {
2468 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
2469 }
2470
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002471 appearance.recycle();
2472 }
2473
2474 /**
Victoria Leasedf8ef4b2012-08-17 15:34:01 -07002475 * Get the default {@link Locale} of the text in this TextView.
2476 * @return the default {@link Locale} of the text in this TextView.
2477 */
2478 public Locale getTextLocale() {
2479 return mTextPaint.getTextLocale();
2480 }
2481
2482 /**
2483 * Set the default {@link Locale} of the text in this TextView to the given value. This value
2484 * is used to choose appropriate typefaces for ambiguous characters. Typically used for CJK
2485 * locales to disambiguate Hanzi/Kanji/Hanja characters.
2486 *
2487 * @param locale the {@link Locale} for drawing text, must not be null.
2488 *
2489 * @see Paint#setTextLocale
2490 */
2491 public void setTextLocale(Locale locale) {
2492 mTextPaint.setTextLocale(locale);
2493 }
2494
2495 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002496 * @return the size (in pixels) of the default text size in this TextView.
2497 */
Fabrice Di Meglioc54da1c2012-04-27 16:16:35 -07002498 @ViewDebug.ExportedProperty(category = "text")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002499 public float getTextSize() {
2500 return mTextPaint.getTextSize();
2501 }
2502
2503 /**
2504 * Set the default text size to the given value, interpreted as "scaled
2505 * pixel" units. This size is adjusted based on the current density and
2506 * user font size preference.
2507 *
2508 * @param size The scaled pixel size.
2509 *
2510 * @attr ref android.R.styleable#TextView_textSize
2511 */
2512 @android.view.RemotableViewMethod
2513 public void setTextSize(float size) {
2514 setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
2515 }
2516
2517 /**
2518 * Set the default text size to a given unit and value. See {@link
2519 * TypedValue} for the possible dimension units.
2520 *
2521 * @param unit The desired dimension unit.
2522 * @param size The desired size in the given units.
2523 *
2524 * @attr ref android.R.styleable#TextView_textSize
2525 */
2526 public void setTextSize(int unit, float size) {
2527 Context c = getContext();
2528 Resources r;
2529
2530 if (c == null)
2531 r = Resources.getSystem();
2532 else
2533 r = c.getResources();
2534
2535 setRawTextSize(TypedValue.applyDimension(
2536 unit, size, r.getDisplayMetrics()));
2537 }
2538
2539 private void setRawTextSize(float size) {
2540 if (size != mTextPaint.getTextSize()) {
2541 mTextPaint.setTextSize(size);
2542
2543 if (mLayout != null) {
2544 nullLayouts();
2545 requestLayout();
2546 invalidate();
2547 }
2548 }
2549 }
2550
2551 /**
2552 * @return the extent by which text is currently being stretched
2553 * horizontally. This will usually be 1.
2554 */
2555 public float getTextScaleX() {
2556 return mTextPaint.getTextScaleX();
2557 }
2558
2559 /**
2560 * Sets the extent by which text should be stretched horizontally.
2561 *
2562 * @attr ref android.R.styleable#TextView_textScaleX
2563 */
2564 @android.view.RemotableViewMethod
2565 public void setTextScaleX(float size) {
2566 if (size != mTextPaint.getTextScaleX()) {
Romain Guy939151f2009-04-08 14:22:40 -07002567 mUserSetTextScaleX = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002568 mTextPaint.setTextScaleX(size);
2569
2570 if (mLayout != null) {
2571 nullLayouts();
2572 requestLayout();
2573 invalidate();
2574 }
2575 }
2576 }
2577
2578 /**
2579 * Sets the typeface and style in which the text should be displayed.
2580 * Note that not all Typeface families actually have bold and italic
2581 * variants, so you may need to use
2582 * {@link #setTypeface(Typeface, int)} to get the appearance
2583 * that you actually want.
2584 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002585 * @see #getTypeface()
2586 *
Raph Leviend570e892012-05-09 11:45:34 -07002587 * @attr ref android.R.styleable#TextView_fontFamily
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002588 * @attr ref android.R.styleable#TextView_typeface
2589 * @attr ref android.R.styleable#TextView_textStyle
2590 */
2591 public void setTypeface(Typeface tf) {
2592 if (mTextPaint.getTypeface() != tf) {
2593 mTextPaint.setTypeface(tf);
2594
2595 if (mLayout != null) {
2596 nullLayouts();
2597 requestLayout();
2598 invalidate();
2599 }
2600 }
2601 }
2602
2603 /**
2604 * @return the current typeface and style in which the text is being
2605 * displayed.
Gilles Debunnef03acef2012-04-30 19:26:19 -07002606 *
2607 * @see #setTypeface(Typeface)
2608 *
Raph Leviend570e892012-05-09 11:45:34 -07002609 * @attr ref android.R.styleable#TextView_fontFamily
Gilles Debunnef03acef2012-04-30 19:26:19 -07002610 * @attr ref android.R.styleable#TextView_typeface
2611 * @attr ref android.R.styleable#TextView_textStyle
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002612 */
2613 public Typeface getTypeface() {
2614 return mTextPaint.getTypeface();
2615 }
2616
2617 /**
2618 * Sets the text color for all the states (normal, selected,
2619 * focused) to be this color.
2620 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002621 * @see #setTextColor(ColorStateList)
2622 * @see #getTextColors()
2623 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002624 * @attr ref android.R.styleable#TextView_textColor
2625 */
2626 @android.view.RemotableViewMethod
2627 public void setTextColor(int color) {
2628 mTextColor = ColorStateList.valueOf(color);
2629 updateTextColors();
2630 }
2631
2632 /**
2633 * Sets the text color.
2634 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002635 * @see #setTextColor(int)
2636 * @see #getTextColors()
2637 * @see #setHintTextColor(ColorStateList)
2638 * @see #setLinkTextColor(ColorStateList)
2639 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002640 * @attr ref android.R.styleable#TextView_textColor
2641 */
2642 public void setTextColor(ColorStateList colors) {
2643 if (colors == null) {
2644 throw new NullPointerException();
2645 }
2646
2647 mTextColor = colors;
2648 updateTextColors();
2649 }
2650
2651 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002652 * Gets the text colors for the different states (normal, selected, focused) of the TextView.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002653 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002654 * @see #setTextColor(ColorStateList)
2655 * @see #setTextColor(int)
2656 *
2657 * @attr ref android.R.styleable#TextView_textColor
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002658 */
2659 public final ColorStateList getTextColors() {
2660 return mTextColor;
2661 }
2662
2663 /**
2664 * <p>Return the current color selected for normal text.</p>
2665 *
2666 * @return Returns the current text color.
2667 */
2668 public final int getCurrentTextColor() {
2669 return mCurTextColor;
2670 }
2671
2672 /**
2673 * Sets the color used to display the selection highlight.
2674 *
2675 * @attr ref android.R.styleable#TextView_textColorHighlight
2676 */
2677 @android.view.RemotableViewMethod
2678 public void setHighlightColor(int color) {
2679 if (mHighlightColor != color) {
2680 mHighlightColor = color;
2681 invalidate();
2682 }
2683 }
2684
2685 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002686 * @return the color used to display the selection highlight
2687 *
2688 * @see #setHighlightColor(int)
2689 *
2690 * @attr ref android.R.styleable#TextView_textColorHighlight
2691 */
2692 public int getHighlightColor() {
2693 return mHighlightColor;
2694 }
2695
2696 /**
Gilles Debunne3473b2b2012-04-20 16:21:10 -07002697 * Sets whether the soft input method will be made visible when this
2698 * TextView gets focused. The default is true.
2699 * @hide
2700 */
2701 @android.view.RemotableViewMethod
2702 public final void setShowSoftInputOnFocus(boolean show) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07002703 createEditorIfNeeded();
Gilles Debunne3473b2b2012-04-20 16:21:10 -07002704 mEditor.mShowSoftInputOnFocus = show;
2705 }
2706
2707 /**
2708 * Returns whether the soft input method will be made visible when this
2709 * TextView gets focused. The default is true.
2710 * @hide
2711 */
2712 public final boolean getShowSoftInputOnFocus() {
2713 // When there is no Editor, return default true value
2714 return mEditor == null || mEditor.mShowSoftInputOnFocus;
2715 }
2716
2717 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002718 * Gives the text a shadow of the specified radius and color, the specified
2719 * distance from its normal position.
2720 *
2721 * @attr ref android.R.styleable#TextView_shadowColor
2722 * @attr ref android.R.styleable#TextView_shadowDx
2723 * @attr ref android.R.styleable#TextView_shadowDy
2724 * @attr ref android.R.styleable#TextView_shadowRadius
2725 */
2726 public void setShadowLayer(float radius, float dx, float dy, int color) {
2727 mTextPaint.setShadowLayer(radius, dx, dy, color);
2728
2729 mShadowRadius = radius;
2730 mShadowDx = dx;
2731 mShadowDy = dy;
2732
Gilles Debunne33b7de852012-03-12 11:57:48 -07002733 // Will change text clip region
Gilles Debunne2d373a12012-04-20 15:32:19 -07002734 if (mEditor != null) mEditor.invalidateTextDisplayList();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002735 invalidate();
2736 }
2737
2738 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002739 * Gets the radius of the shadow layer.
2740 *
2741 * @return the radius of the shadow layer. If 0, the shadow layer is not visible
2742 *
2743 * @see #setShadowLayer(float, float, float, int)
2744 *
2745 * @attr ref android.R.styleable#TextView_shadowRadius
2746 */
2747 public float getShadowRadius() {
2748 return mShadowRadius;
2749 }
2750
2751 /**
2752 * @return the horizontal offset of the shadow layer
2753 *
2754 * @see #setShadowLayer(float, float, float, int)
2755 *
2756 * @attr ref android.R.styleable#TextView_shadowDx
2757 */
2758 public float getShadowDx() {
2759 return mShadowDx;
2760 }
2761
2762 /**
2763 * @return the vertical offset of the shadow layer
2764 *
2765 * @see #setShadowLayer(float, float, float, int)
2766 *
2767 * @attr ref android.R.styleable#TextView_shadowDy
2768 */
2769 public float getShadowDy() {
2770 return mShadowDy;
2771 }
2772
2773 /**
2774 * @return the color of the shadow layer
2775 *
2776 * @see #setShadowLayer(float, float, float, int)
2777 *
2778 * @attr ref android.R.styleable#TextView_shadowColor
2779 */
2780 public int getShadowColor() {
2781 return mTextPaint.shadowColor;
2782 }
2783
2784 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002785 * @return the base paint used for the text. Please use this only to
2786 * consult the Paint's properties and not to change them.
2787 */
2788 public TextPaint getPaint() {
2789 return mTextPaint;
2790 }
2791
2792 /**
2793 * Sets the autolink mask of the text. See {@link
2794 * android.text.util.Linkify#ALL Linkify.ALL} and peers for
2795 * possible values.
2796 *
2797 * @attr ref android.R.styleable#TextView_autoLink
2798 */
2799 @android.view.RemotableViewMethod
2800 public final void setAutoLinkMask(int mask) {
2801 mAutoLinkMask = mask;
2802 }
2803
2804 /**
2805 * Sets whether the movement method will automatically be set to
2806 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
2807 * set to nonzero and links are detected in {@link #setText}.
2808 * The default is true.
2809 *
2810 * @attr ref android.R.styleable#TextView_linksClickable
2811 */
2812 @android.view.RemotableViewMethod
2813 public final void setLinksClickable(boolean whether) {
2814 mLinksClickable = whether;
2815 }
2816
2817 /**
2818 * Returns whether the movement method will automatically be set to
2819 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
2820 * set to nonzero and links are detected in {@link #setText}.
2821 * The default is true.
2822 *
2823 * @attr ref android.R.styleable#TextView_linksClickable
2824 */
2825 public final boolean getLinksClickable() {
2826 return mLinksClickable;
2827 }
2828
2829 /**
2830 * Returns the list of URLSpans attached to the text
2831 * (by {@link Linkify} or otherwise) if any. You can call
2832 * {@link URLSpan#getURL} on them to find where they link to
2833 * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
2834 * to find the region of the text they are attached to.
2835 */
2836 public URLSpan[] getUrls() {
2837 if (mText instanceof Spanned) {
2838 return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
2839 } else {
2840 return new URLSpan[0];
2841 }
2842 }
2843
2844 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002845 * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this
2846 * TextView.
2847 *
2848 * @see #setHintTextColor(ColorStateList)
2849 * @see #getHintTextColors()
2850 * @see #setTextColor(int)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002851 *
2852 * @attr ref android.R.styleable#TextView_textColorHint
2853 */
2854 @android.view.RemotableViewMethod
2855 public final void setHintTextColor(int color) {
2856 mHintTextColor = ColorStateList.valueOf(color);
2857 updateTextColors();
2858 }
2859
2860 /**
2861 * Sets the color of the hint text.
2862 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002863 * @see #getHintTextColors()
2864 * @see #setHintTextColor(int)
2865 * @see #setTextColor(ColorStateList)
2866 * @see #setLinkTextColor(ColorStateList)
2867 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002868 * @attr ref android.R.styleable#TextView_textColorHint
2869 */
2870 public final void setHintTextColor(ColorStateList colors) {
2871 mHintTextColor = colors;
2872 updateTextColors();
2873 }
2874
2875 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002876 * @return the color of the hint text, for the different states of this TextView.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002877 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002878 * @see #setHintTextColor(ColorStateList)
2879 * @see #setHintTextColor(int)
2880 * @see #setTextColor(ColorStateList)
2881 * @see #setLinkTextColor(ColorStateList)
2882 *
2883 * @attr ref android.R.styleable#TextView_textColorHint
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002884 */
2885 public final ColorStateList getHintTextColors() {
2886 return mHintTextColor;
2887 }
2888
2889 /**
2890 * <p>Return the current color selected to paint the hint text.</p>
2891 *
2892 * @return Returns the current hint text color.
2893 */
2894 public final int getCurrentHintTextColor() {
2895 return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
2896 }
2897
2898 /**
2899 * Sets the color of links in the text.
2900 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002901 * @see #setLinkTextColor(ColorStateList)
2902 * @see #getLinkTextColors()
2903 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002904 * @attr ref android.R.styleable#TextView_textColorLink
2905 */
2906 @android.view.RemotableViewMethod
2907 public final void setLinkTextColor(int color) {
2908 mLinkTextColor = ColorStateList.valueOf(color);
2909 updateTextColors();
2910 }
2911
2912 /**
2913 * Sets the color of links in the text.
2914 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002915 * @see #setLinkTextColor(int)
2916 * @see #getLinkTextColors()
2917 * @see #setTextColor(ColorStateList)
2918 * @see #setHintTextColor(ColorStateList)
2919 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002920 * @attr ref android.R.styleable#TextView_textColorLink
2921 */
2922 public final void setLinkTextColor(ColorStateList colors) {
2923 mLinkTextColor = colors;
2924 updateTextColors();
2925 }
2926
2927 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002928 * @return the list of colors used to paint the links in the text, for the different states of
2929 * this TextView
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002930 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002931 * @see #setLinkTextColor(ColorStateList)
2932 * @see #setLinkTextColor(int)
2933 *
2934 * @attr ref android.R.styleable#TextView_textColorLink
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002935 */
2936 public final ColorStateList getLinkTextColors() {
2937 return mLinkTextColor;
2938 }
2939
2940 /**
2941 * Sets the horizontal alignment of the text and the
2942 * vertical gravity that will be used when there is extra space
2943 * in the TextView beyond what is required for the text itself.
2944 *
2945 * @see android.view.Gravity
2946 * @attr ref android.R.styleable#TextView_gravity
2947 */
2948 public void setGravity(int gravity) {
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07002949 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
Fabrice Di Meglio9e3b0022011-06-06 16:30:29 -07002950 gravity |= Gravity.START;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002951 }
2952 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
2953 gravity |= Gravity.TOP;
2954 }
2955
2956 boolean newLayout = false;
2957
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07002958 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) !=
2959 (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002960 newLayout = true;
2961 }
2962
2963 if (gravity != mGravity) {
2964 invalidate();
2965 }
2966
2967 mGravity = gravity;
2968
2969 if (mLayout != null && newLayout) {
2970 // XXX this is heavy-handed because no actual content changes.
2971 int want = mLayout.getWidth();
2972 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
2973
2974 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
2975 mRight - mLeft - getCompoundPaddingLeft() -
2976 getCompoundPaddingRight(), true);
2977 }
2978 }
2979
2980 /**
2981 * Returns the horizontal and vertical alignment of this TextView.
2982 *
2983 * @see android.view.Gravity
2984 * @attr ref android.R.styleable#TextView_gravity
2985 */
2986 public int getGravity() {
2987 return mGravity;
2988 }
2989
2990 /**
2991 * @return the flags on the Paint being used to display the text.
2992 * @see Paint#getFlags
2993 */
2994 public int getPaintFlags() {
2995 return mTextPaint.getFlags();
2996 }
2997
2998 /**
2999 * Sets flags on the Paint being used to display the text and
3000 * reflows the text if they are different from the old flags.
3001 * @see Paint#setFlags
3002 */
3003 @android.view.RemotableViewMethod
3004 public void setPaintFlags(int flags) {
3005 if (mTextPaint.getFlags() != flags) {
3006 mTextPaint.setFlags(flags);
3007
3008 if (mLayout != null) {
3009 nullLayouts();
3010 requestLayout();
3011 invalidate();
3012 }
3013 }
3014 }
3015
3016 /**
3017 * Sets whether the text should be allowed to be wider than the
3018 * View is. If false, it will be wrapped to the width of the View.
3019 *
3020 * @attr ref android.R.styleable#TextView_scrollHorizontally
3021 */
3022 public void setHorizontallyScrolling(boolean whether) {
Gilles Debunne22378292011-08-12 10:38:52 -07003023 if (mHorizontallyScrolling != whether) {
3024 mHorizontallyScrolling = whether;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003025
Gilles Debunne22378292011-08-12 10:38:52 -07003026 if (mLayout != null) {
3027 nullLayouts();
3028 requestLayout();
3029 invalidate();
3030 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003031 }
3032 }
3033
3034 /**
Gilles Debunnef2a02012011-10-27 11:10:14 -07003035 * Returns whether the text is allowed to be wider than the View is.
3036 * If false, the text will be wrapped to the width of the View.
3037 *
3038 * @attr ref android.R.styleable#TextView_scrollHorizontally
3039 * @hide
3040 */
3041 public boolean getHorizontallyScrolling() {
3042 return mHorizontallyScrolling;
3043 }
3044
3045 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003046 * Makes the TextView at least this many lines tall.
3047 *
3048 * Setting this value overrides any other (minimum) height setting. A single line TextView will
3049 * set this value to 1.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003050 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003051 * @see #getMinLines()
3052 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003053 * @attr ref android.R.styleable#TextView_minLines
3054 */
3055 @android.view.RemotableViewMethod
3056 public void setMinLines(int minlines) {
3057 mMinimum = minlines;
3058 mMinMode = LINES;
3059
3060 requestLayout();
3061 invalidate();
3062 }
3063
3064 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003065 * @return the minimum number of lines displayed in this TextView, or -1 if the minimum
3066 * height was set in pixels instead using {@link #setMinHeight(int) or #setHeight(int)}.
3067 *
3068 * @see #setMinLines(int)
3069 *
3070 * @attr ref android.R.styleable#TextView_minLines
3071 */
3072 public int getMinLines() {
3073 return mMinMode == LINES ? mMinimum : -1;
3074 }
3075
3076 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003077 * Makes the TextView at least this many pixels tall.
3078 *
3079 * Setting this value overrides any other (minimum) number of lines setting.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003080 *
3081 * @attr ref android.R.styleable#TextView_minHeight
3082 */
3083 @android.view.RemotableViewMethod
3084 public void setMinHeight(int minHeight) {
3085 mMinimum = minHeight;
3086 mMinMode = PIXELS;
3087
3088 requestLayout();
3089 invalidate();
3090 }
3091
3092 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003093 * @return the minimum height of this TextView expressed in pixels, or -1 if the minimum
3094 * height was set in number of lines instead using {@link #setMinLines(int) or #setLines(int)}.
3095 *
3096 * @see #setMinHeight(int)
3097 *
3098 * @attr ref android.R.styleable#TextView_minHeight
3099 */
3100 public int getMinHeight() {
3101 return mMinMode == PIXELS ? mMinimum : -1;
3102 }
3103
3104 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003105 * Makes the TextView at most this many lines tall.
3106 *
3107 * Setting this value overrides any other (maximum) height setting.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003108 *
3109 * @attr ref android.R.styleable#TextView_maxLines
3110 */
3111 @android.view.RemotableViewMethod
3112 public void setMaxLines(int maxlines) {
3113 mMaximum = maxlines;
3114 mMaxMode = LINES;
3115
3116 requestLayout();
3117 invalidate();
3118 }
3119
3120 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003121 * @return the maximum number of lines displayed in this TextView, or -1 if the maximum
3122 * height was set in pixels instead using {@link #setMaxHeight(int) or #setHeight(int)}.
3123 *
3124 * @see #setMaxLines(int)
3125 *
3126 * @attr ref android.R.styleable#TextView_maxLines
3127 */
3128 public int getMaxLines() {
3129 return mMaxMode == LINES ? mMaximum : -1;
3130 }
3131
3132 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003133 * Makes the TextView at most this many pixels tall. This option is mutually exclusive with the
3134 * {@link #setMaxLines(int)} method.
3135 *
3136 * Setting this value overrides any other (maximum) number of lines setting.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003137 *
3138 * @attr ref android.R.styleable#TextView_maxHeight
3139 */
3140 @android.view.RemotableViewMethod
3141 public void setMaxHeight(int maxHeight) {
3142 mMaximum = maxHeight;
3143 mMaxMode = PIXELS;
3144
3145 requestLayout();
3146 invalidate();
3147 }
3148
3149 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003150 * @return the maximum height of this TextView expressed in pixels, or -1 if the maximum
3151 * height was set in number of lines instead using {@link #setMaxLines(int) or #setLines(int)}.
3152 *
3153 * @see #setMaxHeight(int)
3154 *
3155 * @attr ref android.R.styleable#TextView_maxHeight
3156 */
3157 public int getMaxHeight() {
3158 return mMaxMode == PIXELS ? mMaximum : -1;
3159 }
3160
3161 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003162 * Makes the TextView exactly this many lines tall.
3163 *
3164 * Note that setting this value overrides any other (minimum / maximum) number of lines or
3165 * height setting. A single line TextView will set this value to 1.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003166 *
3167 * @attr ref android.R.styleable#TextView_lines
3168 */
3169 @android.view.RemotableViewMethod
3170 public void setLines(int lines) {
3171 mMaximum = mMinimum = lines;
3172 mMaxMode = mMinMode = LINES;
3173
3174 requestLayout();
3175 invalidate();
3176 }
3177
3178 /**
3179 * Makes the TextView exactly this many pixels tall.
3180 * You could do the same thing by specifying this number in the
3181 * LayoutParams.
3182 *
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003183 * Note that setting this value overrides any other (minimum / maximum) number of lines or
3184 * height setting.
3185 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003186 * @attr ref android.R.styleable#TextView_height
3187 */
3188 @android.view.RemotableViewMethod
3189 public void setHeight(int pixels) {
3190 mMaximum = mMinimum = pixels;
3191 mMaxMode = mMinMode = PIXELS;
3192
3193 requestLayout();
3194 invalidate();
3195 }
3196
3197 /**
3198 * Makes the TextView at least this many ems wide
3199 *
3200 * @attr ref android.R.styleable#TextView_minEms
3201 */
3202 @android.view.RemotableViewMethod
3203 public void setMinEms(int minems) {
3204 mMinWidth = minems;
3205 mMinWidthMode = EMS;
3206
3207 requestLayout();
3208 invalidate();
3209 }
3210
3211 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003212 * @return the minimum width of the TextView, expressed in ems or -1 if the minimum width
3213 * was set in pixels instead (using {@link #setMinWidth(int)} or {@link #setWidth(int)}).
3214 *
3215 * @see #setMinEms(int)
3216 * @see #setEms(int)
3217 *
3218 * @attr ref android.R.styleable#TextView_minEms
3219 */
3220 public int getMinEms() {
3221 return mMinWidthMode == EMS ? mMinWidth : -1;
3222 }
3223
3224 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003225 * Makes the TextView at least this many pixels wide
3226 *
3227 * @attr ref android.R.styleable#TextView_minWidth
3228 */
3229 @android.view.RemotableViewMethod
3230 public void setMinWidth(int minpixels) {
3231 mMinWidth = minpixels;
3232 mMinWidthMode = PIXELS;
3233
3234 requestLayout();
3235 invalidate();
3236 }
3237
3238 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003239 * @return the minimum width of the TextView, in pixels or -1 if the minimum width
3240 * was set in ems instead (using {@link #setMinEms(int)} or {@link #setEms(int)}).
3241 *
3242 * @see #setMinWidth(int)
3243 * @see #setWidth(int)
3244 *
3245 * @attr ref android.R.styleable#TextView_minWidth
3246 */
3247 public int getMinWidth() {
3248 return mMinWidthMode == PIXELS ? mMinWidth : -1;
3249 }
3250
3251 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003252 * Makes the TextView at most this many ems wide
3253 *
3254 * @attr ref android.R.styleable#TextView_maxEms
3255 */
3256 @android.view.RemotableViewMethod
3257 public void setMaxEms(int maxems) {
3258 mMaxWidth = maxems;
3259 mMaxWidthMode = EMS;
3260
3261 requestLayout();
3262 invalidate();
3263 }
3264
3265 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003266 * @return the maximum width of the TextView, expressed in ems or -1 if the maximum width
3267 * was set in pixels instead (using {@link #setMaxWidth(int)} or {@link #setWidth(int)}).
3268 *
3269 * @see #setMaxEms(int)
3270 * @see #setEms(int)
3271 *
3272 * @attr ref android.R.styleable#TextView_maxEms
3273 */
3274 public int getMaxEms() {
3275 return mMaxWidthMode == EMS ? mMaxWidth : -1;
3276 }
3277
3278 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003279 * Makes the TextView at most this many pixels wide
3280 *
3281 * @attr ref android.R.styleable#TextView_maxWidth
3282 */
3283 @android.view.RemotableViewMethod
3284 public void setMaxWidth(int maxpixels) {
3285 mMaxWidth = maxpixels;
3286 mMaxWidthMode = PIXELS;
3287
3288 requestLayout();
3289 invalidate();
3290 }
3291
3292 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003293 * @return the maximum width of the TextView, in pixels or -1 if the maximum width
3294 * was set in ems instead (using {@link #setMaxEms(int)} or {@link #setEms(int)}).
3295 *
3296 * @see #setMaxWidth(int)
3297 * @see #setWidth(int)
3298 *
3299 * @attr ref android.R.styleable#TextView_maxWidth
3300 */
3301 public int getMaxWidth() {
3302 return mMaxWidthMode == PIXELS ? mMaxWidth : -1;
3303 }
3304
3305 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003306 * Makes the TextView exactly this many ems wide
3307 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003308 * @see #setMaxEms(int)
3309 * @see #setMinEms(int)
3310 * @see #getMinEms()
3311 * @see #getMaxEms()
3312 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003313 * @attr ref android.R.styleable#TextView_ems
3314 */
3315 @android.view.RemotableViewMethod
3316 public void setEms(int ems) {
3317 mMaxWidth = mMinWidth = ems;
3318 mMaxWidthMode = mMinWidthMode = EMS;
3319
3320 requestLayout();
3321 invalidate();
3322 }
3323
3324 /**
3325 * Makes the TextView exactly this many pixels wide.
3326 * You could do the same thing by specifying this number in the
3327 * LayoutParams.
3328 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003329 * @see #setMaxWidth(int)
3330 * @see #setMinWidth(int)
3331 * @see #getMinWidth()
3332 * @see #getMaxWidth()
3333 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003334 * @attr ref android.R.styleable#TextView_width
3335 */
3336 @android.view.RemotableViewMethod
3337 public void setWidth(int pixels) {
3338 mMaxWidth = mMinWidth = pixels;
3339 mMaxWidthMode = mMinWidthMode = PIXELS;
3340
3341 requestLayout();
3342 invalidate();
3343 }
3344
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003345 /**
3346 * Sets line spacing for this TextView. Each line will have its height
3347 * multiplied by <code>mult</code> and have <code>add</code> added to it.
3348 *
3349 * @attr ref android.R.styleable#TextView_lineSpacingExtra
3350 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
3351 */
3352 public void setLineSpacing(float add, float mult) {
Gilles Debunne22378292011-08-12 10:38:52 -07003353 if (mSpacingAdd != add || mSpacingMult != mult) {
3354 mSpacingAdd = add;
3355 mSpacingMult = mult;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003356
Gilles Debunne22378292011-08-12 10:38:52 -07003357 if (mLayout != null) {
3358 nullLayouts();
3359 requestLayout();
3360 invalidate();
3361 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003362 }
3363 }
3364
3365 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003366 * Gets the line spacing multiplier
3367 *
3368 * @return the value by which each line's height is multiplied to get its actual height.
3369 *
3370 * @see #setLineSpacing(float, float)
3371 * @see #getLineSpacingExtra()
3372 *
3373 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
3374 */
3375 public float getLineSpacingMultiplier() {
3376 return mSpacingMult;
3377 }
3378
3379 /**
3380 * Gets the line spacing extra space
3381 *
3382 * @return the extra space that is added to the height of each lines of this TextView.
3383 *
3384 * @see #setLineSpacing(float, float)
3385 * @see #getLineSpacingMultiplier()
3386 *
3387 * @attr ref android.R.styleable#TextView_lineSpacingExtra
3388 */
3389 public float getLineSpacingExtra() {
3390 return mSpacingAdd;
3391 }
3392
3393 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003394 * Convenience method: Append the specified text to the TextView's
3395 * display buffer, upgrading it to BufferType.EDITABLE if it was
3396 * not already editable.
3397 */
3398 public final void append(CharSequence text) {
3399 append(text, 0, text.length());
3400 }
3401
3402 /**
3403 * Convenience method: Append the specified text slice to the TextView's
3404 * display buffer, upgrading it to BufferType.EDITABLE if it was
3405 * not already editable.
3406 */
3407 public void append(CharSequence text, int start, int end) {
3408 if (!(mText instanceof Editable)) {
3409 setText(mText, BufferType.EDITABLE);
3410 }
3411
3412 ((Editable) mText).append(text, start, end);
3413 }
3414
3415 private void updateTextColors() {
3416 boolean inval = false;
3417 int color = mTextColor.getColorForState(getDrawableState(), 0);
3418 if (color != mCurTextColor) {
3419 mCurTextColor = color;
3420 inval = true;
3421 }
3422 if (mLinkTextColor != null) {
3423 color = mLinkTextColor.getColorForState(getDrawableState(), 0);
3424 if (color != mTextPaint.linkColor) {
3425 mTextPaint.linkColor = color;
3426 inval = true;
3427 }
3428 }
3429 if (mHintTextColor != null) {
3430 color = mHintTextColor.getColorForState(getDrawableState(), 0);
3431 if (color != mCurHintTextColor && mText.length() == 0) {
3432 mCurHintTextColor = color;
3433 inval = true;
3434 }
3435 }
3436 if (inval) {
Gilles Debunne33b7de852012-03-12 11:57:48 -07003437 // Text needs to be redrawn with the new color
Gilles Debunne2d373a12012-04-20 15:32:19 -07003438 if (mEditor != null) mEditor.invalidateTextDisplayList();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003439 invalidate();
3440 }
3441 }
3442
3443 @Override
3444 protected void drawableStateChanged() {
3445 super.drawableStateChanged();
3446 if (mTextColor != null && mTextColor.isStateful()
3447 || (mHintTextColor != null && mHintTextColor.isStateful())
3448 || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
3449 updateTextColors();
3450 }
3451
3452 final Drawables dr = mDrawables;
3453 if (dr != null) {
3454 int[] state = getDrawableState();
3455 if (dr.mDrawableTop != null && dr.mDrawableTop.isStateful()) {
3456 dr.mDrawableTop.setState(state);
3457 }
3458 if (dr.mDrawableBottom != null && dr.mDrawableBottom.isStateful()) {
3459 dr.mDrawableBottom.setState(state);
3460 }
3461 if (dr.mDrawableLeft != null && dr.mDrawableLeft.isStateful()) {
3462 dr.mDrawableLeft.setState(state);
3463 }
3464 if (dr.mDrawableRight != null && dr.mDrawableRight.isStateful()) {
3465 dr.mDrawableRight.setState(state);
3466 }
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07003467 if (dr.mDrawableStart != null && dr.mDrawableStart.isStateful()) {
3468 dr.mDrawableStart.setState(state);
3469 }
3470 if (dr.mDrawableEnd != null && dr.mDrawableEnd.isStateful()) {
3471 dr.mDrawableEnd.setState(state);
3472 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003473 }
3474 }
3475
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003476 @Override
3477 public Parcelable onSaveInstanceState() {
3478 Parcelable superState = super.onSaveInstanceState();
3479
3480 // Save state if we are forced to
3481 boolean save = mFreezesText;
3482 int start = 0;
3483 int end = 0;
3484
3485 if (mText != null) {
Gilles Debunne05336272010-07-09 20:13:45 -07003486 start = getSelectionStart();
3487 end = getSelectionEnd();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003488 if (start >= 0 || end >= 0) {
3489 // Or save state if there is a selection
3490 save = true;
3491 }
3492 }
3493
3494 if (save) {
3495 SavedState ss = new SavedState(superState);
3496 // XXX Should also save the current scroll position!
3497 ss.selStart = start;
3498 ss.selEnd = end;
3499
3500 if (mText instanceof Spanned) {
Victoria Leaseaf7dcdf2013-10-24 12:35:42 -07003501 Spannable sp = new SpannableStringBuilder(mText);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003502
Gilles Debunne60e21862012-01-30 15:04:14 -08003503 if (mEditor != null) {
3504 removeMisspelledSpans(sp);
Gilles Debunne2d373a12012-04-20 15:32:19 -07003505 sp.removeSpan(mEditor.mSuggestionRangeSpan);
Gilles Debunne60e21862012-01-30 15:04:14 -08003506 }
Gilles Debunneaa67eef2011-06-01 18:03:37 -07003507
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003508 ss.text = sp;
3509 } else {
3510 ss.text = mText.toString();
3511 }
3512
3513 if (isFocused() && start >= 0 && end >= 0) {
3514 ss.frozenWithFocus = true;
3515 }
3516
Gilles Debunne60e21862012-01-30 15:04:14 -08003517 ss.error = getError();
The Android Open Source Project4df24232009-03-05 14:34:35 -08003518
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003519 return ss;
3520 }
3521
3522 return superState;
3523 }
3524
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07003525 void removeMisspelledSpans(Spannable spannable) {
3526 SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(),
3527 SuggestionSpan.class);
3528 for (int i = 0; i < suggestionSpans.length; i++) {
3529 int flags = suggestionSpans[i].getFlags();
3530 if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
3531 && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
3532 spannable.removeSpan(suggestionSpans[i]);
3533 }
3534 }
3535 }
3536
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003537 @Override
3538 public void onRestoreInstanceState(Parcelable state) {
3539 if (!(state instanceof SavedState)) {
3540 super.onRestoreInstanceState(state);
3541 return;
3542 }
3543
3544 SavedState ss = (SavedState)state;
3545 super.onRestoreInstanceState(ss.getSuperState());
3546
3547 // XXX restore buffer type too, as well as lots of other stuff
3548 if (ss.text != null) {
3549 setText(ss.text);
3550 }
3551
3552 if (ss.selStart >= 0 && ss.selEnd >= 0) {
3553 if (mText instanceof Spannable) {
3554 int len = mText.length();
3555
3556 if (ss.selStart > len || ss.selEnd > len) {
3557 String restored = "";
3558
3559 if (ss.text != null) {
3560 restored = "(restored) ";
3561 }
3562
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07003563 Log.e(LOG_TAG, "Saved cursor position " + ss.selStart +
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003564 "/" + ss.selEnd + " out of range for " + restored +
3565 "text " + mText);
3566 } else {
Gilles Debunnec1e79b42012-02-24 17:29:31 -08003567 Selection.setSelection((Spannable) mText, ss.selStart, ss.selEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003568
3569 if (ss.frozenWithFocus) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07003570 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07003571 mEditor.mFrozenWithFocus = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003572 }
3573 }
3574 }
3575 }
The Android Open Source Project4df24232009-03-05 14:34:35 -08003576
3577 if (ss.error != null) {
Romain Guy9bc9fa12009-07-21 16:57:29 -07003578 final CharSequence error = ss.error;
3579 // Display the error later, after the first layout pass
3580 post(new Runnable() {
3581 public void run() {
3582 setError(error);
3583 }
3584 });
The Android Open Source Project4df24232009-03-05 14:34:35 -08003585 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003586 }
3587
3588 /**
3589 * Control whether this text view saves its entire text contents when
3590 * freezing to an icicle, in addition to dynamic state such as cursor
3591 * position. By default this is false, not saving the text. Set to true
3592 * if the text in the text view is not being saved somewhere else in
3593 * persistent storage (such as in a content provider) so that if the
3594 * view is later thawed the user will not lose their data.
3595 *
3596 * @param freezesText Controls whether a frozen icicle should include the
3597 * entire text data: true to include it, false to not.
3598 *
3599 * @attr ref android.R.styleable#TextView_freezesText
3600 */
3601 @android.view.RemotableViewMethod
3602 public void setFreezesText(boolean freezesText) {
3603 mFreezesText = freezesText;
3604 }
3605
3606 /**
3607 * Return whether this text view is including its entire text contents
3608 * in frozen icicles.
3609 *
3610 * @return Returns true if text is included, false if it isn't.
3611 *
3612 * @see #setFreezesText
3613 */
3614 public boolean getFreezesText() {
3615 return mFreezesText;
3616 }
3617
3618 ///////////////////////////////////////////////////////////////////////////
3619
3620 /**
3621 * Sets the Factory used to create new Editables.
3622 */
3623 public final void setEditableFactory(Editable.Factory factory) {
3624 mEditableFactory = factory;
3625 setText(mText);
3626 }
3627
3628 /**
3629 * Sets the Factory used to create new Spannables.
3630 */
3631 public final void setSpannableFactory(Spannable.Factory factory) {
3632 mSpannableFactory = factory;
3633 setText(mText);
3634 }
3635
3636 /**
3637 * Sets the string value of the TextView. TextView <em>does not</em> accept
3638 * HTML-like formatting, which you can do with text strings in XML resource files.
3639 * To style your strings, attach android.text.style.* objects to a
3640 * {@link android.text.SpannableString SpannableString}, or see the
3641 * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
Gilles Debunne21078e42011-08-02 10:22:35 -07003642 * Available Resource Types</a> documentation for an example of setting
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003643 * formatted text in the XML resource file.
3644 *
3645 * @attr ref android.R.styleable#TextView_text
3646 */
3647 @android.view.RemotableViewMethod
3648 public final void setText(CharSequence text) {
3649 setText(text, mBufferType);
3650 }
3651
3652 /**
3653 * Like {@link #setText(CharSequence)},
3654 * except that the cursor position (if any) is retained in the new text.
3655 *
3656 * @param text The new text to place in the text view.
3657 *
3658 * @see #setText(CharSequence)
3659 */
3660 @android.view.RemotableViewMethod
3661 public final void setTextKeepState(CharSequence text) {
3662 setTextKeepState(text, mBufferType);
3663 }
3664
3665 /**
3666 * Sets the text that this TextView is to display (see
3667 * {@link #setText(CharSequence)}) and also sets whether it is stored
3668 * in a styleable/spannable buffer and whether it is editable.
3669 *
3670 * @attr ref android.R.styleable#TextView_text
3671 * @attr ref android.R.styleable#TextView_bufferType
3672 */
3673 public void setText(CharSequence text, BufferType type) {
3674 setText(text, type, true, 0);
3675
3676 if (mCharWrapper != null) {
3677 mCharWrapper.mChars = null;
3678 }
3679 }
3680
3681 private void setText(CharSequence text, BufferType type,
3682 boolean notifyBefore, int oldlen) {
3683 if (text == null) {
3684 text = "";
3685 }
3686
Luca Zanoline0760452011-09-08 12:03:37 +01003687 // If suggestions are not enabled, remove the suggestion spans from the text
3688 if (!isSuggestionsEnabled()) {
3689 text = removeSuggestionSpans(text);
3690 }
3691
Romain Guy939151f2009-04-08 14:22:40 -07003692 if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
3693
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003694 if (text instanceof Spanned &&
3695 ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
Adam Powell282e3772011-08-30 16:51:11 -07003696 if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {
3697 setHorizontalFadingEdgeEnabled(true);
3698 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
3699 } else {
3700 setHorizontalFadingEdgeEnabled(false);
3701 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
3702 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003703 setEllipsize(TextUtils.TruncateAt.MARQUEE);
3704 }
3705
3706 int n = mFilters.length;
3707 for (int i = 0; i < n; i++) {
Gilles Debunnec1714022012-01-17 13:59:23 -08003708 CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003709 if (out != null) {
3710 text = out;
3711 }
3712 }
3713
3714 if (notifyBefore) {
3715 if (mText != null) {
3716 oldlen = mText.length();
3717 sendBeforeTextChanged(mText, 0, oldlen, text.length());
3718 } else {
3719 sendBeforeTextChanged("", 0, 0, text.length());
3720 }
3721 }
3722
3723 boolean needEditableForNotification = false;
3724
3725 if (mListeners != null && mListeners.size() != 0) {
3726 needEditableForNotification = true;
3727 }
3728
Gilles Debunne2d373a12012-04-20 15:32:19 -07003729 if (type == BufferType.EDITABLE || getKeyListener() != null ||
3730 needEditableForNotification) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07003731 createEditorIfNeeded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003732 Editable t = mEditableFactory.newEditable(text);
3733 text = t;
3734 setFilters(t, mFilters);
3735 InputMethodManager imm = InputMethodManager.peekInstance();
3736 if (imm != null) imm.restartInput(this);
3737 } else if (type == BufferType.SPANNABLE || mMovement != null) {
3738 text = mSpannableFactory.newSpannable(text);
3739 } else if (!(text instanceof CharWrapper)) {
3740 text = TextUtils.stringOrSpannedString(text);
3741 }
3742
3743 if (mAutoLinkMask != 0) {
3744 Spannable s2;
3745
3746 if (type == BufferType.EDITABLE || text instanceof Spannable) {
3747 s2 = (Spannable) text;
3748 } else {
3749 s2 = mSpannableFactory.newSpannable(text);
3750 }
3751
3752 if (Linkify.addLinks(s2, mAutoLinkMask)) {
3753 text = s2;
3754 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
3755
3756 /*
3757 * We must go ahead and set the text before changing the
3758 * movement method, because setMovementMethod() may call
3759 * setText() again to try to upgrade the buffer type.
3760 */
3761 mText = text;
3762
Gilles Debunnecbcb3452010-12-17 15:31:02 -08003763 // Do not change the movement method for text that support text selection as it
3764 // would prevent an arbitrary cursor displacement.
Gilles Debunnebb588da2011-07-11 18:26:19 -07003765 if (mLinksClickable && !textCanBeSelected()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003766 setMovementMethod(LinkMovementMethod.getInstance());
3767 }
3768 }
3769 }
3770
3771 mBufferType = type;
3772 mText = text;
3773
Adam Powell7f8f79a2011-07-07 18:35:54 -07003774 if (mTransformation == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003775 mTransformed = text;
Adam Powell7f8f79a2011-07-07 18:35:54 -07003776 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003777 mTransformed = mTransformation.getTransformation(text, this);
Adam Powell7f8f79a2011-07-07 18:35:54 -07003778 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003779
3780 final int textLength = text.length();
3781
Adam Powell7f8f79a2011-07-07 18:35:54 -07003782 if (text instanceof Spannable && !mAllowTransformationLengthChange) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003783 Spannable sp = (Spannable) text;
3784
Gilles Debunnec62589c2012-04-12 14:50:23 -07003785 // Remove any ChangeWatchers that might have come from other TextViews.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003786 final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
3787 final int count = watchers.length;
Gilles Debunnec62589c2012-04-12 14:50:23 -07003788 for (int i = 0; i < count; i++) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003789 sp.removeSpan(watchers[i]);
Gilles Debunnec62589c2012-04-12 14:50:23 -07003790 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003791
Gilles Debunnec62589c2012-04-12 14:50:23 -07003792 if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003793
3794 sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE |
Gilles Debunne60e21862012-01-30 15:04:14 -08003795 (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003796
Gilles Debunnec62589c2012-04-12 14:50:23 -07003797 if (mEditor != null) mEditor.addSpanWatchers(sp);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003798
3799 if (mTransformation != null) {
3800 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003801 }
3802
3803 if (mMovement != null) {
3804 mMovement.initialize(this, (Spannable) text);
3805
3806 /*
3807 * Initializing the movement method will have set the
3808 * selection, so reset mSelectionMoved to keep that from
3809 * interfering with the normal on-focus selection-setting.
3810 */
Gilles Debunne2d373a12012-04-20 15:32:19 -07003811 if (mEditor != null) mEditor.mSelectionMoved = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003812 }
3813 }
3814
3815 if (mLayout != null) {
3816 checkForRelayout();
3817 }
3818
3819 sendOnTextChanged(text, 0, oldlen, textLength);
3820 onTextChanged(text, 0, oldlen, textLength);
3821
Alan Viverette77e9a282013-09-12 17:16:09 -07003822 notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
3823
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003824 if (needEditableForNotification) {
3825 sendAfterTextChanged((Editable) text);
3826 }
Gilles Debunne05336272010-07-09 20:13:45 -07003827
Gilles Debunnebaaace52010-10-01 15:47:13 -07003828 // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
Gilles Debunne2d373a12012-04-20 15:32:19 -07003829 if (mEditor != null) mEditor.prepareCursorControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003830 }
3831
3832 /**
3833 * Sets the TextView to display the specified slice of the specified
3834 * char array. You must promise that you will not change the contents
3835 * of the array except for right before another call to setText(),
3836 * since the TextView has no way to know that the text
3837 * has changed and that it needs to invalidate and re-layout.
3838 */
3839 public final void setText(char[] text, int start, int len) {
3840 int oldlen = 0;
3841
3842 if (start < 0 || len < 0 || start + len > text.length) {
3843 throw new IndexOutOfBoundsException(start + ", " + len);
3844 }
3845
3846 /*
3847 * We must do the before-notification here ourselves because if
3848 * the old text is a CharWrapper we destroy it before calling
3849 * into the normal path.
3850 */
3851 if (mText != null) {
3852 oldlen = mText.length();
3853 sendBeforeTextChanged(mText, 0, oldlen, len);
3854 } else {
3855 sendBeforeTextChanged("", 0, 0, len);
3856 }
3857
3858 if (mCharWrapper == null) {
3859 mCharWrapper = new CharWrapper(text, start, len);
3860 } else {
3861 mCharWrapper.set(text, start, len);
3862 }
3863
3864 setText(mCharWrapper, mBufferType, false, oldlen);
3865 }
3866
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003867 /**
3868 * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)},
3869 * except that the cursor position (if any) is retained in the new text.
3870 *
3871 * @see #setText(CharSequence, android.widget.TextView.BufferType)
3872 */
3873 public final void setTextKeepState(CharSequence text, BufferType type) {
3874 int start = getSelectionStart();
3875 int end = getSelectionEnd();
3876 int len = text.length();
3877
3878 setText(text, type);
3879
3880 if (start >= 0 || end >= 0) {
3881 if (mText instanceof Spannable) {
3882 Selection.setSelection((Spannable) mText,
3883 Math.max(0, Math.min(start, len)),
3884 Math.max(0, Math.min(end, len)));
3885 }
3886 }
3887 }
3888
3889 @android.view.RemotableViewMethod
3890 public final void setText(int resid) {
3891 setText(getContext().getResources().getText(resid));
3892 }
3893
3894 public final void setText(int resid, BufferType type) {
3895 setText(getContext().getResources().getText(resid), type);
3896 }
3897
3898 /**
3899 * Sets the text to be displayed when the text of the TextView is empty.
3900 * Null means to use the normal empty text. The hint does not currently
3901 * participate in determining the size of the view.
3902 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003903 * @attr ref android.R.styleable#TextView_hint
3904 */
3905 @android.view.RemotableViewMethod
3906 public final void setHint(CharSequence hint) {
3907 mHint = TextUtils.stringOrSpannedString(hint);
3908
3909 if (mLayout != null) {
3910 checkForRelayout();
3911 }
3912
Romain Guy4dc4f732009-06-19 15:16:40 -07003913 if (mText.length() == 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003914 invalidate();
Romain Guy4dc4f732009-06-19 15:16:40 -07003915 }
Gilles Debunne626c3162012-02-14 15:46:41 -08003916
Gilles Debunne33b7de852012-03-12 11:57:48 -07003917 // Invalidate display list if hint is currently used
Gilles Debunne60e21862012-01-30 15:04:14 -08003918 if (mEditor != null && mText.length() == 0 && mHint != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07003919 mEditor.invalidateTextDisplayList();
Gilles Debunne60e21862012-01-30 15:04:14 -08003920 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003921 }
3922
3923 /**
3924 * Sets the text to be displayed when the text of the TextView is empty,
3925 * from a resource.
3926 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003927 * @attr ref android.R.styleable#TextView_hint
3928 */
3929 @android.view.RemotableViewMethod
3930 public final void setHint(int resid) {
3931 setHint(getContext().getResources().getText(resid));
3932 }
3933
3934 /**
3935 * Returns the hint that is displayed when the text of the TextView
3936 * is empty.
3937 *
3938 * @attr ref android.R.styleable#TextView_hint
3939 */
3940 @ViewDebug.CapturedViewProperty
3941 public CharSequence getHint() {
3942 return mHint;
3943 }
3944
Gilles Debunned88876a2012-03-16 17:34:04 -07003945 boolean isSingleLine() {
3946 return mSingleLine;
3947 }
3948
Gilles Debunne3784a7f2011-07-15 13:49:38 -07003949 private static boolean isMultilineInputType(int type) {
Gilles Debunne91a08cf2010-11-08 17:34:49 -08003950 return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) ==
3951 (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
3952 }
3953
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003954 /**
Gilles Debunned88876a2012-03-16 17:34:04 -07003955 * Removes the suggestion spans.
3956 */
3957 CharSequence removeSuggestionSpans(CharSequence text) {
3958 if (text instanceof Spanned) {
3959 Spannable spannable;
3960 if (text instanceof Spannable) {
3961 spannable = (Spannable) text;
3962 } else {
3963 spannable = new SpannableString(text);
3964 text = spannable;
3965 }
3966
3967 SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
3968 for (int i = 0; i < spans.length; i++) {
3969 spannable.removeSpan(spans[i]);
3970 }
3971 }
3972 return text;
3973 }
3974
3975 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003976 * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This
3977 * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)},
3978 * to match the given content type. If the given content type is {@link EditorInfo#TYPE_NULL}
3979 * then a soft keyboard will not be displayed for this text view.
3980 *
3981 * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be
3982 * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input
3983 * type.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003984 *
3985 * @see #getInputType()
3986 * @see #setRawInputType(int)
3987 * @see android.text.InputType
3988 * @attr ref android.R.styleable#TextView_inputType
3989 */
3990 public void setInputType(int type) {
Gilles Debunne60e21862012-01-30 15:04:14 -08003991 final boolean wasPassword = isPasswordInputType(getInputType());
3992 final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003993 setInputType(type, false);
Bjorn Bringertad8da912009-09-17 10:47:35 +01003994 final boolean isPassword = isPasswordInputType(type);
3995 final boolean isVisiblePassword = isVisiblePasswordInputType(type);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003996 boolean forceUpdate = false;
3997 if (isPassword) {
3998 setTransformationMethod(PasswordTransformationMethod.getInstance());
Raph Leviend570e892012-05-09 11:45:34 -07003999 setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004000 } else if (isVisiblePassword) {
Amith Yamasania8c0edb2009-09-27 16:51:21 -07004001 if (mTransformation == PasswordTransformationMethod.getInstance()) {
4002 forceUpdate = true;
4003 }
Raph Leviend570e892012-05-09 11:45:34 -07004004 setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004005 } else if (wasPassword || wasVisiblePassword) {
4006 // not in password mode, clean up typeface and transformation
Raph Leviend570e892012-05-09 11:45:34 -07004007 setTypefaceFromAttrs(null /* fontFamily */, -1, -1);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004008 if (mTransformation == PasswordTransformationMethod.getInstance()) {
4009 forceUpdate = true;
4010 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004011 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07004012
Gilles Debunne91a08cf2010-11-08 17:34:49 -08004013 boolean singleLine = !isMultilineInputType(type);
Gilles Debunne2d373a12012-04-20 15:32:19 -07004014
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004015 // We need to update the single line mode if it has changed or we
4016 // were previously in password mode.
Gilles Debunne91a08cf2010-11-08 17:34:49 -08004017 if (mSingleLine != singleLine || forceUpdate) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004018 // Change single line mode, but only change the transformation if
4019 // we are not in password mode.
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08004020 applySingleLine(singleLine, !isPassword, true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004021 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07004022
Luca Zanoline0760452011-09-08 12:03:37 +01004023 if (!isSuggestionsEnabled()) {
4024 mText = removeSuggestionSpans(mText);
4025 }
4026
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004027 InputMethodManager imm = InputMethodManager.peekInstance();
4028 if (imm != null) imm.restartInput(this);
4029 }
4030
Gilles Debunne0dcad2b2010-10-15 16:29:25 -07004031 /**
4032 * It would be better to rely on the input type for everything. A password inputType should have
4033 * a password transformation. We should hence use isPasswordInputType instead of this method.
4034 *
4035 * We should:
4036 * - Call setInputType in setKeyListener instead of changing the input type directly (which
4037 * would install the correct transformation).
4038 * - Refuse the installation of a non-password transformation in setTransformation if the input
4039 * type is password.
4040 *
4041 * However, this is like this for legacy reasons and we cannot break existing apps. This method
4042 * is useful since it matches what the user can see (obfuscated text or not).
4043 *
4044 * @return true if the current transformation method is of the password type.
4045 */
4046 private boolean hasPasswordTransformationMethod() {
4047 return mTransformation instanceof PasswordTransformationMethod;
4048 }
4049
Gilles Debunne3784a7f2011-07-15 13:49:38 -07004050 private static boolean isPasswordInputType(int inputType) {
Gilles Debunned7483bf2010-11-10 10:47:45 -08004051 final int variation =
4052 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004053 return variation
Gilles Debunned7483bf2010-11-10 10:47:45 -08004054 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)
4055 || variation
Ken Wakasa82d731a2010-12-24 23:42:41 +09004056 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD)
4057 || variation
4058 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004059 }
4060
Gilles Debunne3784a7f2011-07-15 13:49:38 -07004061 private static boolean isVisiblePasswordInputType(int inputType) {
Gilles Debunned7483bf2010-11-10 10:47:45 -08004062 final int variation =
4063 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004064 return variation
Gilles Debunned7483bf2010-11-10 10:47:45 -08004065 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004066 }
4067
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004068 /**
4069 * Directly change the content type integer of the text view, without
4070 * modifying any other state.
4071 * @see #setInputType(int)
4072 * @see android.text.InputType
4073 * @attr ref android.R.styleable#TextView_inputType
4074 */
4075 public void setRawInputType(int type) {
Gilles Debunne60e21862012-01-30 15:04:14 -08004076 if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
Gilles Debunne5fae9962012-05-08 14:53:20 -07004077 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004078 mEditor.mInputType = type;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004079 }
4080
4081 private void setInputType(int type, boolean direct) {
4082 final int cls = type & EditorInfo.TYPE_MASK_CLASS;
4083 KeyListener input;
4084 if (cls == EditorInfo.TYPE_CLASS_TEXT) {
Gilles Debunnee67b58a2010-08-31 15:55:31 -07004085 boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004086 TextKeyListener.Capitalize cap;
4087 if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
4088 cap = TextKeyListener.Capitalize.CHARACTERS;
4089 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
4090 cap = TextKeyListener.Capitalize.WORDS;
4091 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
4092 cap = TextKeyListener.Capitalize.SENTENCES;
4093 } else {
4094 cap = TextKeyListener.Capitalize.NONE;
4095 }
4096 input = TextKeyListener.getInstance(autotext, cap);
4097 } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
4098 input = DigitsKeyListener.getInstance(
4099 (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
4100 (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
4101 } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
4102 switch (type & EditorInfo.TYPE_MASK_VARIATION) {
4103 case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
4104 input = DateKeyListener.getInstance();
4105 break;
4106 case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
4107 input = TimeKeyListener.getInstance();
4108 break;
4109 default:
4110 input = DateTimeKeyListener.getInstance();
4111 break;
4112 }
4113 } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
4114 input = DialerKeyListener.getInstance();
4115 } else {
4116 input = TextKeyListener.getInstance();
4117 }
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07004118 setRawInputType(type);
Gilles Debunne60e21862012-01-30 15:04:14 -08004119 if (direct) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004120 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004121 mEditor.mKeyListener = input;
Gilles Debunne60e21862012-01-30 15:04:14 -08004122 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004123 setKeyListenerOnly(input);
4124 }
4125 }
4126
4127 /**
Gilles Debunne60e21862012-01-30 15:04:14 -08004128 * Get the type of the editable content.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004129 *
4130 * @see #setInputType(int)
4131 * @see android.text.InputType
4132 */
4133 public int getInputType() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004134 return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004135 }
4136
4137 /**
4138 * Change the editor type integer associated with the text view, which
4139 * will be reported to an IME with {@link EditorInfo#imeOptions} when it
4140 * has focus.
4141 * @see #getImeOptions
4142 * @see android.view.inputmethod.EditorInfo
4143 * @attr ref android.R.styleable#TextView_imeOptions
4144 */
4145 public void setImeOptions(int imeOptions) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004146 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004147 mEditor.createInputContentTypeIfNeeded();
4148 mEditor.mInputContentType.imeOptions = imeOptions;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004149 }
4150
4151 /**
4152 * Get the type of the IME editor.
4153 *
4154 * @see #setImeOptions(int)
4155 * @see android.view.inputmethod.EditorInfo
4156 */
4157 public int getImeOptions() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004158 return mEditor != null && mEditor.mInputContentType != null
4159 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004160 }
4161
4162 /**
4163 * Change the custom IME action associated with the text view, which
4164 * will be reported to an IME with {@link EditorInfo#actionLabel}
4165 * and {@link EditorInfo#actionId} when it has focus.
4166 * @see #getImeActionLabel
4167 * @see #getImeActionId
4168 * @see android.view.inputmethod.EditorInfo
4169 * @attr ref android.R.styleable#TextView_imeActionLabel
4170 * @attr ref android.R.styleable#TextView_imeActionId
4171 */
4172 public void setImeActionLabel(CharSequence label, int actionId) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004173 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004174 mEditor.createInputContentTypeIfNeeded();
4175 mEditor.mInputContentType.imeActionLabel = label;
4176 mEditor.mInputContentType.imeActionId = actionId;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004177 }
4178
4179 /**
4180 * Get the IME action label previous set with {@link #setImeActionLabel}.
4181 *
4182 * @see #setImeActionLabel
4183 * @see android.view.inputmethod.EditorInfo
4184 */
4185 public CharSequence getImeActionLabel() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004186 return mEditor != null && mEditor.mInputContentType != null
4187 ? mEditor.mInputContentType.imeActionLabel : null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004188 }
4189
4190 /**
4191 * Get the IME action ID previous set with {@link #setImeActionLabel}.
4192 *
4193 * @see #setImeActionLabel
4194 * @see android.view.inputmethod.EditorInfo
4195 */
4196 public int getImeActionId() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004197 return mEditor != null && mEditor.mInputContentType != null
4198 ? mEditor.mInputContentType.imeActionId : 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004199 }
4200
4201 /**
4202 * Set a special listener to be called when an action is performed
4203 * on the text view. This will be called when the enter key is pressed,
4204 * or when an action supplied to the IME is selected by the user. Setting
4205 * this means that the normal hard key event will not insert a newline
4206 * into the text view, even if it is multi-line; holding down the ALT
4207 * modifier will, however, allow the user to insert a newline character.
4208 */
4209 public void setOnEditorActionListener(OnEditorActionListener l) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004210 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004211 mEditor.createInputContentTypeIfNeeded();
4212 mEditor.mInputContentType.onEditorActionListener = l;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004213 }
Gilles Debunne60e21862012-01-30 15:04:14 -08004214
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004215 /**
4216 * Called when an attached input method calls
4217 * {@link InputConnection#performEditorAction(int)
4218 * InputConnection.performEditorAction()}
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07004219 * for this text view. The default implementation will call your action
4220 * listener supplied to {@link #setOnEditorActionListener}, or perform
4221 * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
Dianne Hackborndea3ef72010-10-28 14:24:22 -07004222 * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS
4223 * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07004224 * EditorInfo.IME_ACTION_DONE}.
Gilles Debunne2d373a12012-04-20 15:32:19 -07004225 *
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07004226 * <p>For backwards compatibility, if no IME options have been set and the
4227 * text view would not normally advance focus on enter, then
4228 * the NEXT and DONE actions received here will be turned into an enter
4229 * key down/up pair to go through the normal key handling.
Gilles Debunne2d373a12012-04-20 15:32:19 -07004230 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004231 * @param actionCode The code of the action being performed.
Gilles Debunne2d373a12012-04-20 15:32:19 -07004232 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004233 * @see #setOnEditorActionListener
4234 */
4235 public void onEditorAction(int actionCode) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004236 final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004237 if (ict != null) {
4238 if (ict.onEditorActionListener != null) {
4239 if (ict.onEditorActionListener.onEditorAction(this,
4240 actionCode, null)) {
4241 return;
4242 }
4243 }
Gilles Debunne64794482011-11-30 15:45:28 -08004244
The Android Open Source Project4df24232009-03-05 14:34:35 -08004245 // This is the handling for some default action.
4246 // Note that for backwards compatibility we don't do this
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004247 // default handling if explicit ime options have not been given,
The Android Open Source Project10592532009-03-18 17:39:46 -07004248 // instead turning this into the normal enter key codes that an
The Android Open Source Project4df24232009-03-05 14:34:35 -08004249 // app may be expecting.
4250 if (actionCode == EditorInfo.IME_ACTION_NEXT) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004251 View v = focusSearch(FOCUS_FORWARD);
The Android Open Source Project4df24232009-03-05 14:34:35 -08004252 if (v != null) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004253 if (!v.requestFocus(FOCUS_FORWARD)) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08004254 throw new IllegalStateException("focus search returned a view " +
4255 "that wasn't able to take focus!");
4256 }
4257 }
4258 return;
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004259
Dianne Hackborndea3ef72010-10-28 14:24:22 -07004260 } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004261 View v = focusSearch(FOCUS_BACKWARD);
Dianne Hackborndea3ef72010-10-28 14:24:22 -07004262 if (v != null) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004263 if (!v.requestFocus(FOCUS_BACKWARD)) {
Dianne Hackborndea3ef72010-10-28 14:24:22 -07004264 throw new IllegalStateException("focus search returned a view " +
4265 "that wasn't able to take focus!");
4266 }
4267 }
4268 return;
4269
The Android Open Source Project4df24232009-03-05 14:34:35 -08004270 } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
4271 InputMethodManager imm = InputMethodManager.peekInstance();
Gilles Debunne17d31de2011-01-27 11:02:18 -08004272 if (imm != null && imm.isActive(this)) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08004273 imm.hideSoftInputFromWindow(getWindowToken(), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004274 }
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07004275 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004276 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004277 }
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004278
Jeff Browna175a5b2012-02-15 19:18:31 -08004279 ViewRootImpl viewRootImpl = getViewRootImpl();
4280 if (viewRootImpl != null) {
The Android Open Source Project10592532009-03-18 17:39:46 -07004281 long eventTime = SystemClock.uptimeMillis();
Jeff Browna175a5b2012-02-15 19:18:31 -08004282 viewRootImpl.dispatchKeyFromIme(
The Android Open Source Project10592532009-03-18 17:39:46 -07004283 new KeyEvent(eventTime, eventTime,
Jeff Brown6b53e8d2010-11-10 16:03:06 -08004284 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
4285 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
The Android Open Source Project10592532009-03-18 17:39:46 -07004286 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
Jeff Browna175a5b2012-02-15 19:18:31 -08004287 | KeyEvent.FLAG_EDITOR_ACTION));
4288 viewRootImpl.dispatchKeyFromIme(
The Android Open Source Project10592532009-03-18 17:39:46 -07004289 new KeyEvent(SystemClock.uptimeMillis(), eventTime,
Jeff Brown6b53e8d2010-11-10 16:03:06 -08004290 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
4291 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
The Android Open Source Project10592532009-03-18 17:39:46 -07004292 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
Jeff Browna175a5b2012-02-15 19:18:31 -08004293 | KeyEvent.FLAG_EDITOR_ACTION));
The Android Open Source Project10592532009-03-18 17:39:46 -07004294 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004295 }
Gilles Debunne64794482011-11-30 15:45:28 -08004296
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004297 /**
4298 * Set the private content type of the text, which is the
4299 * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
4300 * field that will be filled in when creating an input connection.
4301 *
4302 * @see #getPrivateImeOptions()
4303 * @see EditorInfo#privateImeOptions
4304 * @attr ref android.R.styleable#TextView_privateImeOptions
4305 */
4306 public void setPrivateImeOptions(String type) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004307 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004308 mEditor.createInputContentTypeIfNeeded();
4309 mEditor.mInputContentType.privateImeOptions = type;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004310 }
4311
4312 /**
4313 * Get the private type of the content.
4314 *
4315 * @see #setPrivateImeOptions(String)
4316 * @see EditorInfo#privateImeOptions
4317 */
4318 public String getPrivateImeOptions() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004319 return mEditor != null && mEditor.mInputContentType != null
4320 ? mEditor.mInputContentType.privateImeOptions : null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004321 }
4322
4323 /**
4324 * Set the extra input data of the text, which is the
4325 * {@link EditorInfo#extras TextBoxAttribute.extras}
4326 * Bundle that will be filled in when creating an input connection. The
4327 * given integer is the resource ID of an XML resource holding an
4328 * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
4329 *
Gilles Debunne2d373a12012-04-20 15:32:19 -07004330 * @see #getInputExtras(boolean)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004331 * @see EditorInfo#extras
4332 * @attr ref android.R.styleable#TextView_editorExtras
4333 */
Gilles Debunne60e21862012-01-30 15:04:14 -08004334 public void setInputExtras(int xmlResId) throws XmlPullParserException, IOException {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004335 createEditorIfNeeded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004336 XmlResourceParser parser = getResources().getXml(xmlResId);
Gilles Debunne2d373a12012-04-20 15:32:19 -07004337 mEditor.createInputContentTypeIfNeeded();
4338 mEditor.mInputContentType.extras = new Bundle();
4339 getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004340 }
4341
4342 /**
4343 * Retrieve the input extras currently associated with the text view, which
4344 * can be viewed as well as modified.
4345 *
4346 * @param create If true, the extras will be created if they don't already
4347 * exist. Otherwise, null will be returned if none have been created.
Gilles Debunnee15b3582010-06-16 15:17:21 -07004348 * @see #setInputExtras(int)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004349 * @see EditorInfo#extras
4350 * @attr ref android.R.styleable#TextView_editorExtras
4351 */
4352 public Bundle getInputExtras(boolean create) {
Gilles Debunne60e21862012-01-30 15:04:14 -08004353 if (mEditor == null && !create) return null;
Gilles Debunne5fae9962012-05-08 14:53:20 -07004354 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004355 if (mEditor.mInputContentType == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004356 if (!create) return null;
Gilles Debunne2d373a12012-04-20 15:32:19 -07004357 mEditor.createInputContentTypeIfNeeded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004358 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07004359 if (mEditor.mInputContentType.extras == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004360 if (!create) return null;
Gilles Debunne2d373a12012-04-20 15:32:19 -07004361 mEditor.mInputContentType.extras = new Bundle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004362 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07004363 return mEditor.mInputContentType.extras;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004364 }
4365
4366 /**
4367 * Returns the error message that was set to be displayed with
4368 * {@link #setError}, or <code>null</code> if no error was set
4369 * or if it the error was cleared by the widget after user input.
4370 */
4371 public CharSequence getError() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004372 return mEditor == null ? null : mEditor.mError;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004373 }
4374
4375 /**
4376 * Sets the right-hand compound drawable of the TextView to the "error"
4377 * icon and sets an error message that will be displayed in a popup when
4378 * the TextView has focus. The icon and error message will be reset to
4379 * null when any key events cause changes to the TextView's text. If the
4380 * <code>error</code> is <code>null</code>, the error message and icon
4381 * will be cleared.
4382 */
4383 @android.view.RemotableViewMethod
4384 public void setError(CharSequence error) {
4385 if (error == null) {
4386 setError(null, null);
4387 } else {
Alan Viverette8eea3ea2014-02-03 18:40:20 -08004388 Drawable dr = getContext().getDrawable(
4389 com.android.internal.R.drawable.indicator_input_error);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004390
4391 dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
4392 setError(error, dr);
4393 }
4394 }
4395
4396 /**
4397 * Sets the right-hand compound drawable of the TextView to the specified
4398 * icon and sets an error message that will be displayed in a popup when
4399 * the TextView has focus. The icon and error message will be reset to
4400 * null when any key events cause changes to the TextView's text. The
4401 * drawable must already have had {@link Drawable#setBounds} set on it.
4402 * If the <code>error</code> is <code>null</code>, the error message will
4403 * be cleared (and you should provide a <code>null</code> icon as well).
4404 */
4405 public void setError(CharSequence error, Drawable icon) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004406 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004407 mEditor.setError(error, icon);
Alan Viverette77e9a282013-09-12 17:16:09 -07004408 notifyViewAccessibilityStateChangedIfNeeded(
4409 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004410 }
4411
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004412 @Override
4413 protected boolean setFrame(int l, int t, int r, int b) {
4414 boolean result = super.setFrame(l, t, r, b);
4415
Gilles Debunne2d373a12012-04-20 15:32:19 -07004416 if (mEditor != null) mEditor.setFrame();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004417
Romain Guy986003d2009-03-25 17:42:35 -07004418 restartMarqueeIfNeeded();
4419
4420 return result;
4421 }
4422
4423 private void restartMarqueeIfNeeded() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004424 if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
4425 mRestartMarquee = false;
4426 startMarquee();
4427 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004428 }
4429
4430 /**
4431 * Sets the list of input filters that will be used if the buffer is
Gilles Debunne60e21862012-01-30 15:04:14 -08004432 * Editable. Has no effect otherwise.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004433 *
4434 * @attr ref android.R.styleable#TextView_maxLength
4435 */
4436 public void setFilters(InputFilter[] filters) {
4437 if (filters == null) {
4438 throw new IllegalArgumentException();
4439 }
4440
4441 mFilters = filters;
4442
4443 if (mText instanceof Editable) {
4444 setFilters((Editable) mText, filters);
4445 }
4446 }
4447
4448 /**
4449 * Sets the list of input filters on the specified Editable,
4450 * and includes mInput in the list if it is an InputFilter.
4451 */
4452 private void setFilters(Editable e, InputFilter[] filters) {
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07004453 if (mEditor != null) {
4454 final boolean undoFilter = mEditor.mUndoInputFilter != null;
4455 final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter;
4456 int num = 0;
4457 if (undoFilter) num++;
4458 if (keyFilter) num++;
4459 if (num > 0) {
4460 InputFilter[] nf = new InputFilter[filters.length + num];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004461
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07004462 System.arraycopy(filters, 0, nf, 0, filters.length);
4463 num = 0;
4464 if (undoFilter) {
4465 nf[filters.length] = mEditor.mUndoInputFilter;
4466 num++;
4467 }
4468 if (keyFilter) {
4469 nf[filters.length + num] = (InputFilter) mEditor.mKeyListener;
4470 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004471
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07004472 e.setFilters(nf);
4473 return;
4474 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004475 }
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07004476 e.setFilters(filters);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004477 }
4478
4479 /**
4480 * Returns the current list of input filters.
Gilles Debunnef03acef2012-04-30 19:26:19 -07004481 *
4482 * @attr ref android.R.styleable#TextView_maxLength
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004483 */
4484 public InputFilter[] getFilters() {
4485 return mFilters;
4486 }
4487
4488 /////////////////////////////////////////////////////////////////////////
4489
Philip Milne7b757812012-09-19 18:13:44 -07004490 private int getBoxHeight(Layout l) {
4491 Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE;
4492 int padding = (l == mHintLayout) ?
4493 getCompoundPaddingTop() + getCompoundPaddingBottom() :
4494 getExtendedPaddingTop() + getExtendedPaddingBottom();
4495 return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom;
4496 }
4497
Gilles Debunned88876a2012-03-16 17:34:04 -07004498 int getVerticalOffset(boolean forceNormal) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004499 int voffset = 0;
4500 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
4501
4502 Layout l = mLayout;
4503 if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
4504 l = mHintLayout;
4505 }
4506
4507 if (gravity != Gravity.TOP) {
Philip Milne7b757812012-09-19 18:13:44 -07004508 int boxht = getBoxHeight(l);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004509 int textht = l.getHeight();
4510
4511 if (textht < boxht) {
4512 if (gravity == Gravity.BOTTOM)
4513 voffset = boxht - textht;
4514 else // (gravity == Gravity.CENTER_VERTICAL)
4515 voffset = (boxht - textht) >> 1;
4516 }
4517 }
4518 return voffset;
4519 }
4520
4521 private int getBottomVerticalOffset(boolean forceNormal) {
4522 int voffset = 0;
4523 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
4524
4525 Layout l = mLayout;
4526 if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
4527 l = mHintLayout;
4528 }
4529
4530 if (gravity != Gravity.BOTTOM) {
Philip Milne7b757812012-09-19 18:13:44 -07004531 int boxht = getBoxHeight(l);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004532 int textht = l.getHeight();
4533
4534 if (textht < boxht) {
4535 if (gravity == Gravity.TOP)
4536 voffset = boxht - textht;
4537 else // (gravity == Gravity.CENTER_VERTICAL)
4538 voffset = (boxht - textht) >> 1;
4539 }
4540 }
4541 return voffset;
4542 }
4543
Gilles Debunned88876a2012-03-16 17:34:04 -07004544 void invalidateCursorPath() {
Gilles Debunne83051b82012-02-24 20:01:13 -08004545 if (mHighlightPathBogus) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004546 invalidateCursor();
4547 } else {
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004548 final int horizontalPadding = getCompoundPaddingLeft();
4549 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004550
Gilles Debunne2d373a12012-04-20 15:32:19 -07004551 if (mEditor.mCursorCount == 0) {
Gilles Debunne60e21862012-01-30 15:04:14 -08004552 synchronized (TEMP_RECTF) {
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004553 /*
4554 * The reason for this concern about the thickness of the
4555 * cursor and doing the floor/ceil on the coordinates is that
4556 * some EditTexts (notably textfields in the Browser) have
4557 * anti-aliased text where not all the characters are
4558 * necessarily at integer-multiple locations. This should
4559 * make sure the entire cursor gets invalidated instead of
4560 * sometimes missing half a pixel.
4561 */
4562 float thick = FloatMath.ceil(mTextPaint.getStrokeWidth());
4563 if (thick < 1.0f) {
4564 thick = 1.0f;
4565 }
4566
4567 thick /= 2.0f;
4568
Gilles Debunne83051b82012-02-24 20:01:13 -08004569 // mHighlightPath is guaranteed to be non null at that point.
4570 mHighlightPath.computeBounds(TEMP_RECTF, false);
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004571
Gilles Debunne60e21862012-01-30 15:04:14 -08004572 invalidate((int) FloatMath.floor(horizontalPadding + TEMP_RECTF.left - thick),
4573 (int) FloatMath.floor(verticalPadding + TEMP_RECTF.top - thick),
4574 (int) FloatMath.ceil(horizontalPadding + TEMP_RECTF.right + thick),
4575 (int) FloatMath.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004576 }
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004577 } else {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004578 for (int i = 0; i < mEditor.mCursorCount; i++) {
4579 Rect bounds = mEditor.mCursorDrawable[i].getBounds();
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004580 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
4581 bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
4582 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004583 }
4584 }
4585 }
4586
Gilles Debunned88876a2012-03-16 17:34:04 -07004587 void invalidateCursor() {
Gilles Debunne05336272010-07-09 20:13:45 -07004588 int where = getSelectionEnd();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004589
4590 invalidateCursor(where, where, where);
4591 }
4592
4593 private void invalidateCursor(int a, int b, int c) {
Gilles Debunne8615ac92011-11-29 15:25:03 -08004594 if (a >= 0 || b >= 0 || c >= 0) {
4595 int start = Math.min(Math.min(a, b), c);
4596 int end = Math.max(Math.max(a, b), c);
Gilles Debunne961ebb92011-12-12 10:16:04 -08004597 invalidateRegion(start, end, true /* Also invalidates blinking cursor */);
Gilles Debunne8615ac92011-11-29 15:25:03 -08004598 }
4599 }
4600
4601 /**
4602 * Invalidates the region of text enclosed between the start and end text offsets.
Gilles Debunne8615ac92011-11-29 15:25:03 -08004603 */
Gilles Debunne961ebb92011-12-12 10:16:04 -08004604 void invalidateRegion(int start, int end, boolean invalidateCursor) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004605 if (mLayout == null) {
4606 invalidate();
4607 } else {
Gilles Debunne8615ac92011-11-29 15:25:03 -08004608 int lineStart = mLayout.getLineForOffset(start);
4609 int top = mLayout.getLineTop(lineStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004610
4611 // This is ridiculous, but the descent from the line above
4612 // can hang down into the line we really want to redraw,
4613 // so we have to invalidate part of the line above to make
4614 // sure everything that needs to be redrawn really is.
4615 // (But not the whole line above, because that would cause
4616 // the same problem with the descenders on the line above it!)
Gilles Debunne8615ac92011-11-29 15:25:03 -08004617 if (lineStart > 0) {
4618 top -= mLayout.getLineDescent(lineStart - 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004619 }
4620
Gilles Debunne8615ac92011-11-29 15:25:03 -08004621 int lineEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004622
Gilles Debunne8615ac92011-11-29 15:25:03 -08004623 if (start == end)
4624 lineEnd = lineStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004625 else
Gilles Debunne8615ac92011-11-29 15:25:03 -08004626 lineEnd = mLayout.getLineForOffset(end);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004627
Gilles Debunne8615ac92011-11-29 15:25:03 -08004628 int bottom = mLayout.getLineBottom(lineEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004629
Gilles Debunne83051b82012-02-24 20:01:13 -08004630 // mEditor can be null in case selection is set programmatically.
4631 if (invalidateCursor && mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004632 for (int i = 0; i < mEditor.mCursorCount; i++) {
4633 Rect bounds = mEditor.mCursorDrawable[i].getBounds();
Gilles Debunne961ebb92011-12-12 10:16:04 -08004634 top = Math.min(top, bounds.top);
4635 bottom = Math.max(bottom, bounds.bottom);
4636 }
4637 }
4638
Gilles Debunne8615ac92011-11-29 15:25:03 -08004639 final int compoundPaddingLeft = getCompoundPaddingLeft();
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004640 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
Gilles Debunne8615ac92011-11-29 15:25:03 -08004641
4642 int left, right;
Gilles Debunne961ebb92011-12-12 10:16:04 -08004643 if (lineStart == lineEnd && !invalidateCursor) {
Gilles Debunne8615ac92011-11-29 15:25:03 -08004644 left = (int) mLayout.getPrimaryHorizontal(start);
4645 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0);
4646 left += compoundPaddingLeft;
4647 right += compoundPaddingLeft;
4648 } else {
4649 // Rectangle bounding box when the region spans several lines
4650 left = compoundPaddingLeft;
4651 right = getWidth() - getCompoundPaddingRight();
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004652 }
4653
Gilles Debunne8615ac92011-11-29 15:25:03 -08004654 invalidate(mScrollX + left, verticalPadding + top,
4655 mScrollX + right, verticalPadding + bottom);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004656 }
4657 }
4658
4659 private void registerForPreDraw() {
Gilles Debunne2e37d622012-01-27 13:54:00 -08004660 if (!mPreDrawRegistered) {
4661 getViewTreeObserver().addOnPreDrawListener(this);
4662 mPreDrawRegistered = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004663 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004664 }
4665
4666 /**
4667 * {@inheritDoc}
4668 */
4669 public boolean onPreDraw() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004670 if (mLayout == null) {
4671 assumeLayout();
4672 }
4673
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004674 if (mMovement != null) {
Gilles Debunne05336272010-07-09 20:13:45 -07004675 /* This code also provides auto-scrolling when a cursor is moved using a
4676 * CursorController (insertion point or selection limits).
4677 * For selection, ensure start or end is visible depending on controller's state.
4678 */
4679 int curs = getSelectionEnd();
Gilles Debunnee587d832010-11-23 20:20:11 -08004680 // Do not create the controller if it is not already created.
Gilles Debunne2d373a12012-04-20 15:32:19 -07004681 if (mEditor != null && mEditor.mSelectionModifierCursorController != null &&
4682 mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07004683 curs = getSelectionStart();
Gilles Debunne05336272010-07-09 20:13:45 -07004684 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004685
4686 /*
4687 * TODO: This should really only keep the end in view if
4688 * it already was before the text changed. I'm not sure
4689 * of a good way to tell from here if it was.
4690 */
Gilles Debunne60e21862012-01-30 15:04:14 -08004691 if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004692 curs = mText.length();
4693 }
4694
4695 if (curs >= 0) {
Raph Leviene048f842013-09-27 13:36:24 -07004696 bringPointIntoView(curs);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004697 }
4698 } else {
Raph Leviene048f842013-09-27 13:36:24 -07004699 bringTextIntoView();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004700 }
4701
Gilles Debunne64e54a62010-09-07 19:07:17 -07004702 // This has to be checked here since:
4703 // - onFocusChanged cannot start it when focus is given to a view with selected text (after
4704 // a screen rotation) since layout is not yet initialized at that point.
Gilles Debunne2d373a12012-04-20 15:32:19 -07004705 if (mEditor != null && mEditor.mCreatedWithASelection) {
4706 mEditor.startSelectionActionMode();
4707 mEditor.mCreatedWithASelection = false;
Gilles Debunnec01f3fe2010-12-22 17:07:36 -08004708 }
4709
4710 // Phone specific code (there is no ExtractEditText on tablets).
4711 // ExtractEditText does not call onFocus when it is displayed, and mHasSelectionOnFocus can
4712 // not be set. Do the test here instead.
Gilles Debunned88876a2012-03-16 17:34:04 -07004713 if (this instanceof ExtractEditText && hasSelection() && mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004714 mEditor.startSelectionActionMode();
Gilles Debunnef788a9f2010-07-22 10:17:23 -07004715 }
Gilles Debunne64e54a62010-09-07 19:07:17 -07004716
Gilles Debunne2e37d622012-01-27 13:54:00 -08004717 getViewTreeObserver().removeOnPreDrawListener(this);
4718 mPreDrawRegistered = false;
4719
Raph Leviene048f842013-09-27 13:36:24 -07004720 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004721 }
4722
4723 @Override
4724 protected void onAttachedToWindow() {
4725 super.onAttachedToWindow();
4726
4727 mTemporaryDetach = false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07004728
Gilles Debunne2d373a12012-04-20 15:32:19 -07004729 if (mEditor != null) mEditor.onAttachedToWindow();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004730 }
4731
4732 @Override
4733 protected void onDetachedFromWindow() {
4734 super.onDetachedFromWindow();
4735
Gilles Debunne2e37d622012-01-27 13:54:00 -08004736 if (mPreDrawRegistered) {
4737 getViewTreeObserver().removeOnPreDrawListener(this);
4738 mPreDrawRegistered = false;
Gilles Debunne81f08082011-02-17 14:07:19 -08004739 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004740
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07004741 resetResolvedDrawables();
Gilles Debunne186aaf92011-09-16 14:26:12 -07004742
Gilles Debunne2d373a12012-04-20 15:32:19 -07004743 if (mEditor != null) mEditor.onDetachedFromWindow();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004744 }
4745
4746 @Override
Romain Guybb9908b2012-03-08 11:14:07 -08004747 public void onScreenStateChanged(int screenState) {
4748 super.onScreenStateChanged(screenState);
Gilles Debunne2d373a12012-04-20 15:32:19 -07004749 if (mEditor != null) mEditor.onScreenStateChanged(screenState);
Romain Guybb9908b2012-03-08 11:14:07 -08004750 }
4751
4752 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004753 protected boolean isPaddingOffsetRequired() {
Romain Guy076dc9f2009-06-24 17:17:51 -07004754 return mShadowRadius != 0 || mDrawables != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004755 }
4756
4757 @Override
4758 protected int getLeftPaddingOffset() {
Romain Guy076dc9f2009-06-24 17:17:51 -07004759 return getCompoundPaddingLeft() - mPaddingLeft +
4760 (int) Math.min(0, mShadowDx - mShadowRadius);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004761 }
4762
4763 @Override
4764 protected int getTopPaddingOffset() {
4765 return (int) Math.min(0, mShadowDy - mShadowRadius);
4766 }
4767
4768 @Override
4769 protected int getBottomPaddingOffset() {
4770 return (int) Math.max(0, mShadowDy + mShadowRadius);
4771 }
4772
4773 @Override
4774 protected int getRightPaddingOffset() {
Romain Guy076dc9f2009-06-24 17:17:51 -07004775 return -(getCompoundPaddingRight() - mPaddingRight) +
4776 (int) Math.max(0, mShadowDx + mShadowRadius);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004777 }
4778
4779 @Override
4780 protected boolean verifyDrawable(Drawable who) {
4781 final boolean verified = super.verifyDrawable(who);
4782 if (!verified && mDrawables != null) {
4783 return who == mDrawables.mDrawableLeft || who == mDrawables.mDrawableTop ||
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07004784 who == mDrawables.mDrawableRight || who == mDrawables.mDrawableBottom ||
4785 who == mDrawables.mDrawableStart || who == mDrawables.mDrawableEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004786 }
4787 return verified;
4788 }
4789
4790 @Override
Dianne Hackborne2136772010-11-04 15:08:59 -07004791 public void jumpDrawablesToCurrentState() {
4792 super.jumpDrawablesToCurrentState();
4793 if (mDrawables != null) {
4794 if (mDrawables.mDrawableLeft != null) {
4795 mDrawables.mDrawableLeft.jumpToCurrentState();
4796 }
4797 if (mDrawables.mDrawableTop != null) {
4798 mDrawables.mDrawableTop.jumpToCurrentState();
4799 }
4800 if (mDrawables.mDrawableRight != null) {
4801 mDrawables.mDrawableRight.jumpToCurrentState();
4802 }
4803 if (mDrawables.mDrawableBottom != null) {
4804 mDrawables.mDrawableBottom.jumpToCurrentState();
4805 }
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07004806 if (mDrawables.mDrawableStart != null) {
4807 mDrawables.mDrawableStart.jumpToCurrentState();
4808 }
4809 if (mDrawables.mDrawableEnd != null) {
4810 mDrawables.mDrawableEnd.jumpToCurrentState();
4811 }
Dianne Hackborne2136772010-11-04 15:08:59 -07004812 }
4813 }
4814
4815 @Override
Romain Guy3c77d392009-05-20 11:26:50 -07004816 public void invalidateDrawable(Drawable drawable) {
Alan Viverettee6875f12014-02-05 14:05:17 -08004817 boolean handled = false;
4818
Romain Guy3c77d392009-05-20 11:26:50 -07004819 if (verifyDrawable(drawable)) {
4820 final Rect dirty = drawable.getBounds();
4821 int scrollX = mScrollX;
4822 int scrollY = mScrollY;
4823
4824 // IMPORTANT: The coordinates below are based on the coordinates computed
4825 // for each compound drawable in onDraw(). Make sure to update each section
4826 // accordingly.
4827 final TextView.Drawables drawables = mDrawables;
Romain Guya6cd4e02009-05-20 15:09:21 -07004828 if (drawables != null) {
4829 if (drawable == drawables.mDrawableLeft) {
4830 final int compoundPaddingTop = getCompoundPaddingTop();
4831 final int compoundPaddingBottom = getCompoundPaddingBottom();
4832 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
Romain Guy3c77d392009-05-20 11:26:50 -07004833
Romain Guya6cd4e02009-05-20 15:09:21 -07004834 scrollX += mPaddingLeft;
4835 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
Alan Viverettee6875f12014-02-05 14:05:17 -08004836 handled = true;
Romain Guya6cd4e02009-05-20 15:09:21 -07004837 } else if (drawable == drawables.mDrawableRight) {
4838 final int compoundPaddingTop = getCompoundPaddingTop();
4839 final int compoundPaddingBottom = getCompoundPaddingBottom();
4840 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
Romain Guy3c77d392009-05-20 11:26:50 -07004841
Romain Guya6cd4e02009-05-20 15:09:21 -07004842 scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
4843 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
Alan Viverettee6875f12014-02-05 14:05:17 -08004844 handled = true;
Romain Guya6cd4e02009-05-20 15:09:21 -07004845 } else if (drawable == drawables.mDrawableTop) {
4846 final int compoundPaddingLeft = getCompoundPaddingLeft();
4847 final int compoundPaddingRight = getCompoundPaddingRight();
4848 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
Romain Guy3c77d392009-05-20 11:26:50 -07004849
Romain Guya6cd4e02009-05-20 15:09:21 -07004850 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
4851 scrollY += mPaddingTop;
Alan Viverettee6875f12014-02-05 14:05:17 -08004852 handled = true;
Romain Guya6cd4e02009-05-20 15:09:21 -07004853 } else if (drawable == drawables.mDrawableBottom) {
4854 final int compoundPaddingLeft = getCompoundPaddingLeft();
4855 final int compoundPaddingRight = getCompoundPaddingRight();
4856 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
Romain Guy3c77d392009-05-20 11:26:50 -07004857
Romain Guya6cd4e02009-05-20 15:09:21 -07004858 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
4859 scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
Alan Viverettee6875f12014-02-05 14:05:17 -08004860 handled = true;
Romain Guya6cd4e02009-05-20 15:09:21 -07004861 }
Romain Guy3c77d392009-05-20 11:26:50 -07004862 }
4863
Alan Viverettee6875f12014-02-05 14:05:17 -08004864 if (handled) {
4865 invalidate(dirty.left + scrollX, dirty.top + scrollY,
4866 dirty.right + scrollX, dirty.bottom + scrollY);
4867 }
4868 }
4869
4870 if (!handled) {
4871 super.invalidateDrawable(drawable);
Romain Guy3c77d392009-05-20 11:26:50 -07004872 }
4873 }
4874
4875 @Override
Chet Haasedb8c9a62012-03-21 18:54:18 -07004876 public boolean hasOverlappingRendering() {
Chris Craik7bcde502013-10-11 12:51:11 -07004877 // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation
Michael Jurka0931a852013-03-21 16:07:45 +01004878 return ((getBackground() != null && getBackground().getCurrent() != null)
Chris Craik7bcde502013-10-11 12:51:11 -07004879 || mText instanceof Spannable || hasSelection()
4880 || isHorizontalFadingEdgeEnabled());
Chet Haasedb8c9a62012-03-21 18:54:18 -07004881 }
4882
Gilles Debunne86b9c782010-11-11 10:43:48 -08004883 /**
Gilles Debunne86b9c782010-11-11 10:43:48 -08004884 *
Joe Malin10d96952013-05-29 17:49:09 -07004885 * Returns the state of the {@code textIsSelectable} flag (See
4886 * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag
4887 * to allow users to select and copy text in a non-editable TextView, the content of an
4888 * {@link EditText} can always be selected, independently of the value of this flag.
4889 * <p>
Gilles Debunne86b9c782010-11-11 10:43:48 -08004890 *
4891 * @return True if the text displayed in this TextView can be selected by the user.
4892 *
4893 * @attr ref android.R.styleable#TextView_textIsSelectable
4894 */
4895 public boolean isTextSelectable() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004896 return mEditor == null ? false : mEditor.mTextIsSelectable;
Gilles Debunne86b9c782010-11-11 10:43:48 -08004897 }
4898
4899 /**
Joe Malin10d96952013-05-29 17:49:09 -07004900 * Sets whether the content of this view is selectable by the user. The default is
4901 * {@code false}, meaning that the content is not selectable.
4902 * <p>
4903 * When you use a TextView to display a useful piece of information to the user (such as a
4904 * contact's address), make it selectable, so that the user can select and copy its
4905 * content. You can also use set the XML attribute
4906 * {@link android.R.styleable#TextView_textIsSelectable} to "true".
4907 * <p>
4908 * When you call this method to set the value of {@code textIsSelectable}, it sets
4909 * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable},
4910 * and {@code longClickable} to the same value. These flags correspond to the attributes
4911 * {@link android.R.styleable#View_focusable android:focusable},
4912 * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode},
4913 * {@link android.R.styleable#View_clickable android:clickable}, and
4914 * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these
4915 * flags to a state you had set previously, call one or more of the following methods:
4916 * {@link #setFocusable(boolean) setFocusable()},
4917 * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()},
4918 * {@link #setClickable(boolean) setClickable()} or
4919 * {@link #setLongClickable(boolean) setLongClickable()}.
Gilles Debunne60e21862012-01-30 15:04:14 -08004920 *
Joe Malin10d96952013-05-29 17:49:09 -07004921 * @param selectable Whether the content of this TextView should be selectable.
Gilles Debunne86b9c782010-11-11 10:43:48 -08004922 */
4923 public void setTextIsSelectable(boolean selectable) {
Gilles Debunne60e21862012-01-30 15:04:14 -08004924 if (!selectable && mEditor == null) return; // false is default value with no edit data
Gilles Debunne86b9c782010-11-11 10:43:48 -08004925
Gilles Debunne5fae9962012-05-08 14:53:20 -07004926 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004927 if (mEditor.mTextIsSelectable == selectable) return;
Gilles Debunne86b9c782010-11-11 10:43:48 -08004928
Gilles Debunne2d373a12012-04-20 15:32:19 -07004929 mEditor.mTextIsSelectable = selectable;
Gilles Debunnecbcb3452010-12-17 15:31:02 -08004930 setFocusableInTouchMode(selectable);
Gilles Debunne86b9c782010-11-11 10:43:48 -08004931 setFocusable(selectable);
4932 setClickable(selectable);
4933 setLongClickable(selectable);
4934
Gilles Debunne60e21862012-01-30 15:04:14 -08004935 // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
Gilles Debunne86b9c782010-11-11 10:43:48 -08004936
4937 setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
Gilles Debunne857c3412012-06-07 10:50:58 -07004938 setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
Gilles Debunne86b9c782010-11-11 10:43:48 -08004939
4940 // Called by setText above, but safer in case of future code changes
Gilles Debunne2d373a12012-04-20 15:32:19 -07004941 mEditor.prepareCursorControllers();
Gilles Debunne86b9c782010-11-11 10:43:48 -08004942 }
4943
4944 @Override
4945 protected int[] onCreateDrawableState(int extraSpace) {
Gilles Debunnefb817032011-01-13 13:52:49 -08004946 final int[] drawableState;
Gilles Debunneda0a3f02010-12-22 10:07:15 -08004947
Gilles Debunnefb817032011-01-13 13:52:49 -08004948 if (mSingleLine) {
4949 drawableState = super.onCreateDrawableState(extraSpace);
4950 } else {
4951 drawableState = super.onCreateDrawableState(extraSpace + 1);
Gilles Debunneda0a3f02010-12-22 10:07:15 -08004952 mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
4953 }
4954
Gilles Debunne60e21862012-01-30 15:04:14 -08004955 if (isTextSelectable()) {
Gilles Debunne86b9c782010-11-11 10:43:48 -08004956 // Disable pressed state, which was introduced when TextView was made clickable.
4957 // Prevents text color change.
4958 // setClickable(false) would have a similar effect, but it also disables focus changes
4959 // and long press actions, which are both needed by text selection.
4960 final int length = drawableState.length;
4961 for (int i = 0; i < length; i++) {
4962 if (drawableState[i] == R.attr.state_pressed) {
4963 final int[] nonPressedState = new int[length - 1];
4964 System.arraycopy(drawableState, 0, nonPressedState, 0, i);
4965 System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
4966 return nonPressedState;
4967 }
4968 }
4969 }
Gilles Debunneda0a3f02010-12-22 10:07:15 -08004970
Gilles Debunne86b9c782010-11-11 10:43:48 -08004971 return drawableState;
4972 }
4973
Gilles Debunne83051b82012-02-24 20:01:13 -08004974 private Path getUpdatedHighlightPath() {
4975 Path highlight = null;
4976 Paint highlightPaint = mHighlightPaint;
4977
4978 final int selStart = getSelectionStart();
4979 final int selEnd = getSelectionEnd();
4980 if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
4981 if (selStart == selEnd) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004982 if (mEditor != null && mEditor.isCursorVisible() &&
4983 (SystemClock.uptimeMillis() - mEditor.mShowCursor) %
Gilles Debunned88876a2012-03-16 17:34:04 -07004984 (2 * Editor.BLINK) < Editor.BLINK) {
Gilles Debunne83051b82012-02-24 20:01:13 -08004985 if (mHighlightPathBogus) {
4986 if (mHighlightPath == null) mHighlightPath = new Path();
4987 mHighlightPath.reset();
4988 mLayout.getCursorPath(selStart, mHighlightPath, mText);
Gilles Debunne2d373a12012-04-20 15:32:19 -07004989 mEditor.updateCursorsPositions();
Gilles Debunne83051b82012-02-24 20:01:13 -08004990 mHighlightPathBogus = false;
4991 }
4992
4993 // XXX should pass to skin instead of drawing directly
4994 highlightPaint.setColor(mCurTextColor);
Gilles Debunne83051b82012-02-24 20:01:13 -08004995 highlightPaint.setStyle(Paint.Style.STROKE);
4996 highlight = mHighlightPath;
4997 }
4998 } else {
4999 if (mHighlightPathBogus) {
5000 if (mHighlightPath == null) mHighlightPath = new Path();
5001 mHighlightPath.reset();
5002 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
5003 mHighlightPathBogus = false;
5004 }
5005
5006 // XXX should pass to skin instead of drawing directly
5007 highlightPaint.setColor(mHighlightColor);
Gilles Debunne83051b82012-02-24 20:01:13 -08005008 highlightPaint.setStyle(Paint.Style.FILL);
5009
5010 highlight = mHighlightPath;
5011 }
5012 }
5013 return highlight;
5014 }
5015
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -08005016 /**
5017 * @hide
5018 */
5019 public int getHorizontalOffsetForDrawables() {
5020 return 0;
5021 }
5022
Romain Guyc4d8eb62010-08-18 20:48:33 -07005023 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005024 protected void onDraw(Canvas canvas) {
Romain Guy986003d2009-03-25 17:42:35 -07005025 restartMarqueeIfNeeded();
5026
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005027 // Draw the background for this view
5028 super.onDraw(canvas);
5029
5030 final int compoundPaddingLeft = getCompoundPaddingLeft();
5031 final int compoundPaddingTop = getCompoundPaddingTop();
5032 final int compoundPaddingRight = getCompoundPaddingRight();
5033 final int compoundPaddingBottom = getCompoundPaddingBottom();
5034 final int scrollX = mScrollX;
5035 final int scrollY = mScrollY;
5036 final int right = mRight;
5037 final int left = mLeft;
5038 final int bottom = mBottom;
5039 final int top = mTop;
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -08005040 final boolean isLayoutRtl = isLayoutRtl();
5041 final int offset = getHorizontalOffsetForDrawables();
5042 final int leftOffset = isLayoutRtl ? 0 : offset;
5043 final int rightOffset = isLayoutRtl ? offset : 0 ;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005044
5045 final Drawables dr = mDrawables;
5046 if (dr != null) {
5047 /*
5048 * Compound, not extended, because the icon is not clipped
5049 * if the text height is smaller.
5050 */
5051
5052 int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
5053 int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
5054
Romain Guy3c77d392009-05-20 11:26:50 -07005055 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5056 // Make sure to update invalidateDrawable() when changing this code.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005057 if (dr.mDrawableLeft != null) {
5058 canvas.save();
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -08005059 canvas.translate(scrollX + mPaddingLeft + leftOffset,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005060 scrollY + compoundPaddingTop +
5061 (vspace - dr.mDrawableHeightLeft) / 2);
5062 dr.mDrawableLeft.draw(canvas);
5063 canvas.restore();
5064 }
5065
Romain Guy3c77d392009-05-20 11:26:50 -07005066 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5067 // Make sure to update invalidateDrawable() when changing this code.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005068 if (dr.mDrawableRight != null) {
5069 canvas.save();
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -08005070 canvas.translate(scrollX + right - left - mPaddingRight
5071 - dr.mDrawableSizeRight - rightOffset,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005072 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
5073 dr.mDrawableRight.draw(canvas);
5074 canvas.restore();
5075 }
5076
Romain Guy3c77d392009-05-20 11:26:50 -07005077 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5078 // Make sure to update invalidateDrawable() when changing this code.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005079 if (dr.mDrawableTop != null) {
5080 canvas.save();
Gilles Debunne2d373a12012-04-20 15:32:19 -07005081 canvas.translate(scrollX + compoundPaddingLeft +
5082 (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005083 dr.mDrawableTop.draw(canvas);
5084 canvas.restore();
5085 }
5086
Romain Guy3c77d392009-05-20 11:26:50 -07005087 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5088 // Make sure to update invalidateDrawable() when changing this code.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005089 if (dr.mDrawableBottom != null) {
5090 canvas.save();
5091 canvas.translate(scrollX + compoundPaddingLeft +
5092 (hspace - dr.mDrawableWidthBottom) / 2,
5093 scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
5094 dr.mDrawableBottom.draw(canvas);
5095 canvas.restore();
5096 }
5097 }
5098
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005099 int color = mCurTextColor;
5100
5101 if (mLayout == null) {
5102 assumeLayout();
5103 }
5104
5105 Layout layout = mLayout;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005106
5107 if (mHint != null && mText.length() == 0) {
5108 if (mHintTextColor != null) {
5109 color = mCurHintTextColor;
5110 }
5111
5112 layout = mHintLayout;
5113 }
5114
5115 mTextPaint.setColor(color);
5116 mTextPaint.drawableState = getDrawableState();
5117
5118 canvas.save();
5119 /* Would be faster if we didn't have to do this. Can we chop the
5120 (displayable) text so that we don't need to do this ever?
5121 */
5122
5123 int extendedPaddingTop = getExtendedPaddingTop();
5124 int extendedPaddingBottom = getExtendedPaddingBottom();
5125
Fabrice Di Meglio132bda12012-02-07 17:02:00 -08005126 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
5127 final int maxScrollY = mLayout.getHeight() - vspace;
5128
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005129 float clipLeft = compoundPaddingLeft + scrollX;
Fabrice Di Meglio132bda12012-02-07 17:02:00 -08005130 float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005131 float clipRight = right - left - compoundPaddingRight + scrollX;
Fabrice Di Meglio132bda12012-02-07 17:02:00 -08005132 float clipBottom = bottom - top + scrollY -
5133 ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005134
5135 if (mShadowRadius != 0) {
5136 clipLeft += Math.min(0, mShadowDx - mShadowRadius);
5137 clipRight += Math.max(0, mShadowDx + mShadowRadius);
5138
5139 clipTop += Math.min(0, mShadowDy - mShadowRadius);
5140 clipBottom += Math.max(0, mShadowDy + mShadowRadius);
5141 }
5142
5143 canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
5144
5145 int voffsetText = 0;
5146 int voffsetCursor = 0;
5147
5148 // translate in by our padding
Gilles Debunne60e21862012-01-30 15:04:14 -08005149 /* shortcircuit calling getVerticaOffset() */
5150 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5151 voffsetText = getVerticalOffset(false);
5152 voffsetCursor = getVerticalOffset(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005153 }
Gilles Debunne60e21862012-01-30 15:04:14 -08005154 canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005155
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07005156 final int layoutDirection = getLayoutDirection();
Fabrice Di Meglioc0053222011-06-13 12:16:51 -07005157 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
Adam Powell282e3772011-08-30 16:51:11 -07005158 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
5159 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005160 if (!mSingleLine && getLineCount() == 1 && canMarquee() &&
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07005161 (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07005162 final int width = mRight - mLeft;
5163 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
5164 final float dx = mLayout.getLineRight(0) - (width - padding);
5165 canvas.translate(isLayoutRtl ? -dx : +dx, 0.0f);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005166 }
5167
5168 if (mMarquee != null && mMarquee.isRunning()) {
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07005169 final float dx = -mMarquee.getScroll();
5170 canvas.translate(isLayoutRtl ? -dx : +dx, 0.0f);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005171 }
5172 }
5173
Gilles Debunne12d91ce2010-12-10 11:36:29 -08005174 final int cursorOffsetVertical = voffsetCursor - voffsetText;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005175
Gilles Debunne83051b82012-02-24 20:01:13 -08005176 Path highlight = getUpdatedHighlightPath();
Gilles Debunne60e21862012-01-30 15:04:14 -08005177 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005178 mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
Gilles Debunneb35ab7b2011-12-05 15:54:00 -08005179 } else {
Gilles Debunne83051b82012-02-24 20:01:13 -08005180 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
Gilles Debunned88876a2012-03-16 17:34:04 -07005181 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005182
Gilles Debunned88876a2012-03-16 17:34:04 -07005183 if (mMarquee != null && mMarquee.shouldDrawGhost()) {
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07005184 final int dx = (int) mMarquee.getGhostOffset();
5185 canvas.translate(isLayoutRtl ? -dx : dx, 0.0f);
Gilles Debunned88876a2012-03-16 17:34:04 -07005186 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
Romain Guyc2303192009-04-03 17:37:18 -07005187 }
5188
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005189 canvas.restore();
Leon Scroggins56426252010-11-01 15:45:37 -04005190 }
5191
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005192 @Override
5193 public void getFocusedRect(Rect r) {
5194 if (mLayout == null) {
5195 super.getFocusedRect(r);
5196 return;
5197 }
5198
Dianne Hackborn70a3f672011-08-08 14:32:41 -07005199 int selEnd = getSelectionEnd();
5200 if (selEnd < 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005201 super.getFocusedRect(r);
5202 return;
5203 }
5204
Dianne Hackborn70a3f672011-08-08 14:32:41 -07005205 int selStart = getSelectionStart();
5206 if (selStart < 0 || selStart >= selEnd) {
5207 int line = mLayout.getLineForOffset(selEnd);
5208 r.top = mLayout.getLineTop(line);
5209 r.bottom = mLayout.getLineBottom(line);
5210 r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
5211 r.right = r.left + 4;
5212 } else {
5213 int lineStart = mLayout.getLineForOffset(selStart);
5214 int lineEnd = mLayout.getLineForOffset(selEnd);
5215 r.top = mLayout.getLineTop(lineStart);
5216 r.bottom = mLayout.getLineBottom(lineEnd);
5217 if (lineStart == lineEnd) {
5218 r.left = (int) mLayout.getPrimaryHorizontal(selStart);
5219 r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
5220 } else {
Gilles Debunne60e21862012-01-30 15:04:14 -08005221 // Selection extends across multiple lines -- make the focused
5222 // rect cover the entire width.
Gilles Debunne83051b82012-02-24 20:01:13 -08005223 if (mHighlightPathBogus) {
5224 if (mHighlightPath == null) mHighlightPath = new Path();
5225 mHighlightPath.reset();
5226 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
5227 mHighlightPathBogus = false;
5228 }
5229 synchronized (TEMP_RECTF) {
5230 mHighlightPath.computeBounds(TEMP_RECTF, true);
5231 r.left = (int)TEMP_RECTF.left-1;
5232 r.right = (int)TEMP_RECTF.right+1;
Dianne Hackborn70a3f672011-08-08 14:32:41 -07005233 }
5234 }
5235 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005236
5237 // Adjust for padding and gravity.
5238 int paddingLeft = getCompoundPaddingLeft();
5239 int paddingTop = getExtendedPaddingTop();
5240 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5241 paddingTop += getVerticalOffset(false);
5242 }
5243 r.offset(paddingLeft, paddingTop);
Gilles Debunne322044a2012-02-22 12:01:40 -08005244 int paddingBottom = getExtendedPaddingBottom();
5245 r.bottom += paddingBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005246 }
5247
5248 /**
5249 * Return the number of lines of text, or 0 if the internal Layout has not
5250 * been built.
5251 */
5252 public int getLineCount() {
5253 return mLayout != null ? mLayout.getLineCount() : 0;
5254 }
5255
5256 /**
5257 * Return the baseline for the specified line (0...getLineCount() - 1)
5258 * If bounds is not null, return the top, left, right, bottom extents
5259 * of the specified line in it. If the internal Layout has not been built,
5260 * return 0 and set bounds to (0, 0, 0, 0)
5261 * @param line which line to examine (0..getLineCount() - 1)
5262 * @param bounds Optional. If not null, it returns the extent of the line
5263 * @return the Y-coordinate of the baseline
5264 */
5265 public int getLineBounds(int line, Rect bounds) {
5266 if (mLayout == null) {
5267 if (bounds != null) {
5268 bounds.set(0, 0, 0, 0);
5269 }
5270 return 0;
5271 }
5272 else {
5273 int baseline = mLayout.getLineBounds(line, bounds);
5274
5275 int voffset = getExtendedPaddingTop();
5276 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5277 voffset += getVerticalOffset(true);
5278 }
5279 if (bounds != null) {
5280 bounds.offset(getCompoundPaddingLeft(), voffset);
5281 }
5282 return baseline + voffset;
5283 }
5284 }
5285
5286 @Override
5287 public int getBaseline() {
5288 if (mLayout == null) {
5289 return super.getBaseline();
5290 }
5291
5292 int voffset = 0;
5293 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5294 voffset = getVerticalOffset(true);
5295 }
5296
Philip Milne7b757812012-09-19 18:13:44 -07005297 if (isLayoutModeOptical(mParent)) {
5298 voffset -= getOpticalInsets().top;
5299 }
5300
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005301 return getExtendedPaddingTop() + voffset + mLayout.getLineBaseline(0);
5302 }
5303
Romain Guyf2fc4602011-07-19 15:20:03 -07005304 /**
5305 * @hide
Romain Guyf2fc4602011-07-19 15:20:03 -07005306 */
5307 @Override
5308 protected int getFadeTop(boolean offsetRequired) {
Romain Guy59f13c7d2011-07-19 18:35:33 -07005309 if (mLayout == null) return 0;
5310
Romain Guyf2fc4602011-07-19 15:20:03 -07005311 int voffset = 0;
5312 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5313 voffset = getVerticalOffset(true);
5314 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005315
Romain Guyf2fc4602011-07-19 15:20:03 -07005316 if (offsetRequired) voffset += getTopPaddingOffset();
5317
5318 return getExtendedPaddingTop() + voffset;
5319 }
5320
5321 /**
5322 * @hide
Romain Guyf2fc4602011-07-19 15:20:03 -07005323 */
Gilles Debunne3784a7f2011-07-15 13:49:38 -07005324 @Override
Romain Guyf2fc4602011-07-19 15:20:03 -07005325 protected int getFadeHeight(boolean offsetRequired) {
5326 return mLayout != null ? mLayout.getHeight() : 0;
5327 }
5328
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005329 @Override
Gilles Debunne4a7199a2011-07-11 14:58:27 -07005330 public boolean onKeyPreIme(int keyCode, KeyEvent event) {
5331 if (keyCode == KeyEvent.KEYCODE_BACK) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005332 boolean isInSelectionMode = mEditor != null && mEditor.mSelectionActionMode != null;
Gilles Debunne4a7199a2011-07-11 14:58:27 -07005333
Gilles Debunne28294cc2011-08-24 12:02:05 -07005334 if (isInSelectionMode) {
Gilles Debunne4a7199a2011-07-11 14:58:27 -07005335 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
5336 KeyEvent.DispatcherState state = getKeyDispatcherState();
5337 if (state != null) {
5338 state.startTracking(event, this);
5339 }
5340 return true;
5341 } else if (event.getAction() == KeyEvent.ACTION_UP) {
5342 KeyEvent.DispatcherState state = getKeyDispatcherState();
5343 if (state != null) {
5344 state.handleUpEvent(event);
5345 }
5346 if (event.isTracking() && !event.isCanceled()) {
Gilles Debunne14568c32012-01-13 15:26:05 -08005347 stopSelectionActionMode();
5348 return true;
Gilles Debunne4a7199a2011-07-11 14:58:27 -07005349 }
5350 }
5351 }
5352 }
5353 return super.onKeyPreIme(keyCode, event);
5354 }
5355
5356 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005357 public boolean onKeyDown(int keyCode, KeyEvent event) {
5358 int which = doKeyDown(keyCode, event, null);
5359 if (which == 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005360 return super.onKeyDown(keyCode, event);
5361 }
5362
5363 return true;
5364 }
5365
5366 @Override
5367 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005368 KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005369
5370 int which = doKeyDown(keyCode, down, event);
5371 if (which == 0) {
5372 // Go through default dispatching.
5373 return super.onKeyMultiple(keyCode, repeatCount, event);
5374 }
5375 if (which == -1) {
5376 // Consumed the whole thing.
5377 return true;
5378 }
5379
5380 repeatCount--;
Gilles Debunne2d373a12012-04-20 15:32:19 -07005381
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005382 // We are going to dispatch the remaining events to either the input
5383 // or movement method. To do this, we will just send a repeated stream
5384 // of down and up events until we have done the complete repeatCount.
5385 // It would be nice if those interfaces had an onKeyMultiple() method,
5386 // but adding that is a more complicated change.
The Android Open Source Project10592532009-03-18 17:39:46 -07005387 KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005388 if (which == 1) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005389 // mEditor and mEditor.mInput are not null from doKeyDown
5390 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005391 while (--repeatCount > 0) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005392 mEditor.mKeyListener.onKeyDown(this, (Editable)mText, keyCode, down);
5393 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005394 }
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08005395 hideErrorIfUnchanged();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005396
5397 } else if (which == 2) {
Gilles Debunne60e21862012-01-30 15:04:14 -08005398 // mMovement is not null from doKeyDown
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005399 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
5400 while (--repeatCount > 0) {
5401 mMovement.onKeyDown(this, (Spannable)mText, keyCode, down);
5402 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
5403 }
5404 }
5405
5406 return true;
5407 }
5408
5409 /**
5410 * Returns true if pressing ENTER in this field advances focus instead
5411 * of inserting the character. This is true mostly in single-line fields,
5412 * but also in mail addresses and subjects which will display on multiple
5413 * lines but where it doesn't make sense to insert newlines.
5414 */
The Android Open Source Project4df24232009-03-05 14:34:35 -08005415 private boolean shouldAdvanceFocusOnEnter() {
Gilles Debunne60e21862012-01-30 15:04:14 -08005416 if (getKeyListener() == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005417 return false;
5418 }
5419
5420 if (mSingleLine) {
5421 return true;
5422 }
5423
Gilles Debunne2d373a12012-04-20 15:32:19 -07005424 if (mEditor != null &&
5425 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
5426 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005427 if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
5428 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005429 return true;
5430 }
5431 }
5432
5433 return false;
5434 }
5435
Jeff Brown4e6319b2010-12-13 10:36:51 -08005436 /**
5437 * Returns true if pressing TAB in this field advances focus instead
5438 * of inserting the character. Insert tabs only in multi-line editors.
5439 */
5440 private boolean shouldAdvanceFocusOnTab() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005441 if (getKeyListener() != null && !mSingleLine && mEditor != null &&
5442 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
5443 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
5444 if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
5445 || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
5446 return false;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005447 }
5448 }
5449 return true;
5450 }
5451
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005452 private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
5453 if (!isEnabled()) {
5454 return 0;
5455 }
5456
Michael Wright3a7e4832013-02-11 15:55:50 -08005457 // If this is the initial keydown, we don't want to prevent a movement away from this view.
5458 // While this shouldn't be necessary because any time we're preventing default movement we
5459 // should be restricting the focus to remain within this view, thus we'll also receive
5460 // the key up event, occasionally key up events will get dropped and we don't want to
5461 // prevent the user from traversing out of this on the next key down.
5462 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
5463 mPreventDefaultMovement = false;
5464 }
5465
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005466 switch (keyCode) {
5467 case KeyEvent.KEYCODE_ENTER:
Jeff Brown4e6319b2010-12-13 10:36:51 -08005468 if (event.hasNoModifiers()) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005469 // When mInputContentType is set, we know that we are
5470 // running in a "modern" cupcake environment, so don't need
5471 // to worry about the application trying to capture
5472 // enter key events.
Gilles Debunne2d373a12012-04-20 15:32:19 -07005473 if (mEditor != null && mEditor.mInputContentType != null) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005474 // If there is an action listener, given them a
5475 // chance to consume the event.
Gilles Debunne2d373a12012-04-20 15:32:19 -07005476 if (mEditor.mInputContentType.onEditorActionListener != null &&
5477 mEditor.mInputContentType.onEditorActionListener.onEditorAction(
The Android Open Source Project10592532009-03-18 17:39:46 -07005478 this, EditorInfo.IME_NULL, event)) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005479 mEditor.mInputContentType.enterDown = true;
The Android Open Source Project10592532009-03-18 17:39:46 -07005480 // We are consuming the enter key for them.
5481 return -1;
5482 }
5483 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08005484
The Android Open Source Project10592532009-03-18 17:39:46 -07005485 // If our editor should move focus when enter is pressed, or
5486 // this is a generated event from an IME action button, then
5487 // don't let it be inserted into the text.
Jeff Brown4e6319b2010-12-13 10:36:51 -08005488 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
The Android Open Source Project10592532009-03-18 17:39:46 -07005489 || shouldAdvanceFocusOnEnter()) {
Dianne Hackborn0500b3c2011-11-01 15:28:43 -07005490 if (hasOnClickListeners()) {
Leon Scroggins7014b122011-01-11 15:17:34 -05005491 return 0;
5492 }
The Android Open Source Project10592532009-03-18 17:39:46 -07005493 return -1;
5494 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005495 }
The Android Open Source Project10592532009-03-18 17:39:46 -07005496 break;
Gilles Debunne2d373a12012-04-20 15:32:19 -07005497
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005498 case KeyEvent.KEYCODE_DPAD_CENTER:
Jeff Brown4e6319b2010-12-13 10:36:51 -08005499 if (event.hasNoModifiers()) {
5500 if (shouldAdvanceFocusOnEnter()) {
5501 return 0;
5502 }
5503 }
5504 break;
5505
5506 case KeyEvent.KEYCODE_TAB:
5507 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
5508 if (shouldAdvanceFocusOnTab()) {
5509 return 0;
5510 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005511 }
Gilles Debunne64e54a62010-09-07 19:07:17 -07005512 break;
5513
5514 // Has to be done on key down (and not on key up) to correctly be intercepted.
5515 case KeyEvent.KEYCODE_BACK:
Gilles Debunne2d373a12012-04-20 15:32:19 -07005516 if (mEditor != null && mEditor.mSelectionActionMode != null) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07005517 stopSelectionActionMode();
5518 return -1;
5519 }
5520 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005521 }
5522
Gilles Debunne2d373a12012-04-20 15:32:19 -07005523 if (mEditor != null && mEditor.mKeyListener != null) {
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08005524 resetErrorChangedFlag();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005525
5526 boolean doDown = true;
5527 if (otherEvent != null) {
5528 try {
5529 beginBatchEdit();
Gilles Debunne2d373a12012-04-20 15:32:19 -07005530 final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText,
5531 otherEvent);
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08005532 hideErrorIfUnchanged();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005533 doDown = false;
5534 if (handled) {
5535 return -1;
5536 }
5537 } catch (AbstractMethodError e) {
5538 // onKeyOther was added after 1.0, so if it isn't
5539 // implemented we need to try to dispatch as a regular down.
5540 } finally {
5541 endBatchEdit();
5542 }
5543 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005544
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005545 if (doDown) {
5546 beginBatchEdit();
Gilles Debunne2d373a12012-04-20 15:32:19 -07005547 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText,
5548 keyCode, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005549 endBatchEdit();
Gilles Debunne12ab6452011-01-30 12:08:25 -08005550 hideErrorIfUnchanged();
5551 if (handled) return 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005552 }
5553 }
5554
5555 // bug 650865: sometimes we get a key event before a layout.
5556 // don't try to move around if we don't know the layout.
5557
5558 if (mMovement != null && mLayout != null) {
5559 boolean doDown = true;
5560 if (otherEvent != null) {
5561 try {
5562 boolean handled = mMovement.onKeyOther(this, (Spannable) mText,
5563 otherEvent);
5564 doDown = false;
5565 if (handled) {
5566 return -1;
5567 }
5568 } catch (AbstractMethodError e) {
5569 // onKeyOther was added after 1.0, so if it isn't
5570 // implemented we need to try to dispatch as a regular down.
5571 }
5572 }
5573 if (doDown) {
Michael Wright3a7e4832013-02-11 15:55:50 -08005574 if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event)) {
5575 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
5576 mPreventDefaultMovement = true;
5577 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005578 return 2;
Michael Wright3a7e4832013-02-11 15:55:50 -08005579 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005580 }
5581 }
5582
Michael Wright3a7e4832013-02-11 15:55:50 -08005583 return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode) ? -1 : 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005584 }
5585
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08005586 /**
5587 * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
5588 * can be recorded.
5589 * @hide
5590 */
5591 public void resetErrorChangedFlag() {
5592 /*
5593 * Keep track of what the error was before doing the input
5594 * so that if an input filter changed the error, we leave
5595 * that error showing. Otherwise, we take down whatever
5596 * error was showing when the user types something.
5597 */
Gilles Debunne2d373a12012-04-20 15:32:19 -07005598 if (mEditor != null) mEditor.mErrorWasChanged = false;
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08005599 }
5600
5601 /**
5602 * @hide
5603 */
5604 public void hideErrorIfUnchanged() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005605 if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) {
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08005606 setError(null, null);
5607 }
5608 }
5609
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005610 @Override
5611 public boolean onKeyUp(int keyCode, KeyEvent event) {
5612 if (!isEnabled()) {
5613 return super.onKeyUp(keyCode, event);
5614 }
5615
Michael Wright3a7e4832013-02-11 15:55:50 -08005616 if (!KeyEvent.isModifierKey(keyCode)) {
5617 mPreventDefaultMovement = false;
5618 }
5619
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005620 switch (keyCode) {
5621 case KeyEvent.KEYCODE_DPAD_CENTER:
Jeff Brown4e6319b2010-12-13 10:36:51 -08005622 if (event.hasNoModifiers()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005623 /*
5624 * If there is a click listener, just call through to
5625 * super, which will invoke it.
5626 *
Jeff Brown4e6319b2010-12-13 10:36:51 -08005627 * If there isn't a click listener, try to show the soft
5628 * input method. (It will also
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005629 * call performClick(), but that won't do anything in
5630 * this case.)
5631 */
Gilles Debunne06a8e9b2011-12-08 10:39:39 -08005632 if (!hasOnClickListeners()) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08005633 if (mMovement != null && mText instanceof Editable
5634 && mLayout != null && onCheckIsTextEditor()) {
Gilles Debunne17d31de2011-01-27 11:02:18 -08005635 InputMethodManager imm = InputMethodManager.peekInstance();
satoka67a3cf2011-09-07 17:14:03 +09005636 viewClicked(imm);
Gilles Debunne3473b2b2012-04-20 16:21:10 -07005637 if (imm != null && getShowSoftInputOnFocus()) {
satok863fcd62011-06-21 17:38:02 +09005638 imm.showSoftInput(this, 0);
5639 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08005640 }
5641 }
5642 }
5643 return super.onKeyUp(keyCode, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005644
Jeff Brown4e6319b2010-12-13 10:36:51 -08005645 case KeyEvent.KEYCODE_ENTER:
Jeff Brown4e6319b2010-12-13 10:36:51 -08005646 if (event.hasNoModifiers()) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005647 if (mEditor != null && mEditor.mInputContentType != null
5648 && mEditor.mInputContentType.onEditorActionListener != null
5649 && mEditor.mInputContentType.enterDown) {
5650 mEditor.mInputContentType.enterDown = false;
5651 if (mEditor.mInputContentType.onEditorActionListener.onEditorAction(
Jeff Brown4e6319b2010-12-13 10:36:51 -08005652 this, EditorInfo.IME_NULL, event)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005653 return true;
5654 }
5655 }
5656
Jeff Brown4e6319b2010-12-13 10:36:51 -08005657 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
5658 || shouldAdvanceFocusOnEnter()) {
5659 /*
5660 * If there is a click listener, just call through to
5661 * super, which will invoke it.
5662 *
5663 * If there isn't a click listener, try to advance focus,
5664 * but still call through to super, which will reset the
5665 * pressed state and longpress state. (It will also
5666 * call performClick(), but that won't do anything in
5667 * this case.)
5668 */
Gilles Debunne06a8e9b2011-12-08 10:39:39 -08005669 if (!hasOnClickListeners()) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08005670 View v = focusSearch(FOCUS_DOWN);
5671
5672 if (v != null) {
5673 if (!v.requestFocus(FOCUS_DOWN)) {
5674 throw new IllegalStateException(
5675 "focus search returned a view " +
5676 "that wasn't able to take focus!");
5677 }
5678
5679 /*
5680 * Return true because we handled the key; super
5681 * will return false because there was no click
5682 * listener.
5683 */
5684 super.onKeyUp(keyCode, event);
5685 return true;
5686 } else if ((event.getFlags()
5687 & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
5688 // No target for next focus, but make sure the IME
5689 // if this came from it.
5690 InputMethodManager imm = InputMethodManager.peekInstance();
Gilles Debunne17d31de2011-01-27 11:02:18 -08005691 if (imm != null && imm.isActive(this)) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08005692 imm.hideSoftInputFromWindow(getWindowToken(), 0);
5693 }
5694 }
5695 }
5696 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005697 return super.onKeyUp(keyCode, event);
5698 }
Gilles Debunne64e54a62010-09-07 19:07:17 -07005699 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005700 }
5701
Gilles Debunne2d373a12012-04-20 15:32:19 -07005702 if (mEditor != null && mEditor.mKeyListener != null)
5703 if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event))
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005704 return true;
5705
5706 if (mMovement != null && mLayout != null)
5707 if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event))
5708 return true;
5709
5710 return super.onKeyUp(keyCode, event);
5711 }
5712
Gilles Debunnec1714022012-01-17 13:59:23 -08005713 @Override
5714 public boolean onCheckIsTextEditor() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005715 return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005716 }
Gilles Debunneb062e812011-09-27 14:58:37 -07005717
Gilles Debunnec1714022012-01-17 13:59:23 -08005718 @Override
5719 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
Janos Levai042856c2010-10-15 02:53:58 +03005720 if (onCheckIsTextEditor() && isEnabled()) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005721 mEditor.createInputMethodStateIfNeeded();
Gilles Debunne60e21862012-01-30 15:04:14 -08005722 outAttrs.inputType = getInputType();
Gilles Debunne2d373a12012-04-20 15:32:19 -07005723 if (mEditor.mInputContentType != null) {
5724 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
5725 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
5726 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
5727 outAttrs.actionId = mEditor.mInputContentType.imeActionId;
5728 outAttrs.extras = mEditor.mInputContentType.extras;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005729 } else {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005730 outAttrs.imeOptions = EditorInfo.IME_NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005731 }
Dianne Hackborndea3ef72010-10-28 14:24:22 -07005732 if (focusSearch(FOCUS_DOWN) != null) {
5733 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
5734 }
5735 if (focusSearch(FOCUS_UP) != null) {
5736 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
5737 }
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005738 if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION)
5739 == EditorInfo.IME_ACTION_UNSPECIFIED) {
Dianne Hackborndea3ef72010-10-28 14:24:22 -07005740 if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005741 // An action has not been set, but the enter key will move to
5742 // the next focus, so set the action to that.
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005743 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
The Android Open Source Project4df24232009-03-05 14:34:35 -08005744 } else {
5745 // An action has not been set, and there is no focus to move
5746 // to, so let's just supply a "done" action.
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005747 outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
The Android Open Source Project4df24232009-03-05 14:34:35 -08005748 }
5749 if (!shouldAdvanceFocusOnEnter()) {
5750 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005751 }
5752 }
Gilles Debunne91a08cf2010-11-08 17:34:49 -08005753 if (isMultilineInputType(outAttrs.inputType)) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005754 // Multi-line text editors should always show an enter key.
5755 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
5756 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005757 outAttrs.hintText = mHint;
5758 if (mText instanceof Editable) {
5759 InputConnection ic = new EditableInputConnection(this);
Gilles Debunne05336272010-07-09 20:13:45 -07005760 outAttrs.initialSelStart = getSelectionStart();
5761 outAttrs.initialSelEnd = getSelectionEnd();
Gilles Debunne60e21862012-01-30 15:04:14 -08005762 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005763 return ic;
5764 }
5765 }
5766 return null;
5767 }
5768
5769 /**
5770 * If this TextView contains editable content, extract a portion of it
5771 * based on the information in <var>request</var> in to <var>outText</var>.
5772 * @return Returns true if the text was successfully extracted, else false.
5773 */
Gilles Debunned88876a2012-03-16 17:34:04 -07005774 public boolean extractText(ExtractedTextRequest request, ExtractedText outText) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07005775 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07005776 return mEditor.extractText(request, outText);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005777 }
Viktor Yakovel964be412010-02-17 08:35:57 +01005778
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005779 /**
5780 * This is used to remove all style-impacting spans from text before new
5781 * extracted text is being replaced into it, so that we don't have any
5782 * lingering spans applied during the replace.
5783 */
5784 static void removeParcelableSpans(Spannable spannable, int start, int end) {
5785 Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
5786 int i = spans.length;
5787 while (i > 0) {
5788 i--;
5789 spannable.removeSpan(spans[i]);
5790 }
5791 }
Gilles Debunned88876a2012-03-16 17:34:04 -07005792
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005793 /**
5794 * Apply to this text view the given extracted text, as previously
5795 * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
5796 */
5797 public void setExtractedText(ExtractedText text) {
5798 Editable content = getEditableText();
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005799 if (text.text != null) {
5800 if (content == null) {
5801 setText(text.text, TextView.BufferType.EDITABLE);
5802 } else if (text.partialStartOffset < 0) {
5803 removeParcelableSpans(content, 0, content.length());
5804 content.replace(0, content.length(), text.text);
5805 } else {
5806 final int N = content.length();
5807 int start = text.partialStartOffset;
5808 if (start > N) start = N;
5809 int end = text.partialEndOffset;
5810 if (end > N) end = N;
5811 removeParcelableSpans(content, start, end);
5812 content.replace(start, end, text.text);
5813 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005814 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005815
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005816 // Now set the selection position... make sure it is in range, to
5817 // avoid crashes. If this is a partial update, it is possible that
5818 // the underlying text may have changed, causing us problems here.
5819 // Also we just don't want to trust clients to do the right thing.
5820 Spannable sp = (Spannable)getText();
5821 final int N = sp.length();
5822 int start = text.selectionStart;
5823 if (start < 0) start = 0;
5824 else if (start > N) start = N;
5825 int end = text.selectionEnd;
5826 if (end < 0) end = 0;
5827 else if (end > N) end = N;
5828 Selection.setSelection(sp, start, end);
Gilles Debunne2d373a12012-04-20 15:32:19 -07005829
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005830 // Finally, update the selection mode.
5831 if ((text.flags&ExtractedText.FLAG_SELECTING) != 0) {
5832 MetaKeyKeyListener.startSelecting(this, sp);
5833 } else {
5834 MetaKeyKeyListener.stopSelecting(this, sp);
5835 }
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 * @hide
5840 */
5841 public void setExtracting(ExtractedTextRequest req) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005842 if (mEditor.mInputMethodState != null) {
5843 mEditor.mInputMethodState.mExtractedTextRequest = req;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005844 }
Gilles Debunne98fb9ed2011-09-07 17:15:41 -07005845 // This would stop a possible selection mode, but no such mode is started in case
5846 // extracted mode will start. Some text is selected though, and will trigger an action mode
5847 // in the extracted view.
Gilles Debunne2d373a12012-04-20 15:32:19 -07005848 mEditor.hideControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005849 }
Gilles Debunne98fb9ed2011-09-07 17:15:41 -07005850
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005851 /**
5852 * Called by the framework in response to a text completion from
5853 * the current input method, provided by it calling
5854 * {@link InputConnection#commitCompletion
5855 * InputConnection.commitCompletion()}. The default implementation does
5856 * nothing; text views that are supporting auto-completion should override
5857 * this to do their desired behavior.
5858 *
5859 * @param text The auto complete text the user has selected.
5860 */
5861 public void onCommitCompletion(CompletionInfo text) {
Gilles Debunned40cc3c2011-03-11 16:59:32 -08005862 // intentionally empty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005863 }
5864
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08005865 /**
5866 * Called by the framework in response to a text auto-correction (such as fixing a typo using a
5867 * a dictionnary) from the current input method, provided by it calling
5868 * {@link InputConnection#commitCorrection} InputConnection.commitCorrection()}. The default
5869 * implementation flashes the background of the corrected word to provide feedback to the user.
5870 *
5871 * @param info The auto correct info about the text that was corrected.
5872 */
5873 public void onCommitCorrection(CorrectionInfo info) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005874 if (mEditor != null) mEditor.onCommitCorrection(info);
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08005875 }
5876
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005877 public void beginBatchEdit() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005878 if (mEditor != null) mEditor.beginBatchEdit();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005879 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005880
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005881 public void endBatchEdit() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005882 if (mEditor != null) mEditor.endBatchEdit();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005883 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005884
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005885 /**
5886 * Called by the framework in response to a request to begin a batch
5887 * of edit operations through a call to link {@link #beginBatchEdit()}.
5888 */
5889 public void onBeginBatchEdit() {
Gilles Debunned40cc3c2011-03-11 16:59:32 -08005890 // intentionally empty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005891 }
Gilles Debunne60e21862012-01-30 15:04:14 -08005892
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005893 /**
5894 * Called by the framework in response to a request to end a batch
5895 * of edit operations through a call to link {@link #endBatchEdit}.
5896 */
5897 public void onEndBatchEdit() {
Gilles Debunned40cc3c2011-03-11 16:59:32 -08005898 // intentionally empty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005899 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005900
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005901 /**
5902 * Called by the framework in response to a private command from the
5903 * current method, provided by it calling
5904 * {@link InputConnection#performPrivateCommand
5905 * InputConnection.performPrivateCommand()}.
5906 *
5907 * @param action The action name of the command.
5908 * @param data Any additional data for the command. This may be null.
5909 * @return Return true if you handled the command, else false.
5910 */
5911 public boolean onPrivateIMECommand(String action, Bundle data) {
5912 return false;
5913 }
5914
5915 private void nullLayouts() {
5916 if (mLayout instanceof BoringLayout && mSavedLayout == null) {
5917 mSavedLayout = (BoringLayout) mLayout;
5918 }
5919 if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
5920 mSavedHintLayout = (BoringLayout) mHintLayout;
5921 }
5922
Adam Powell282e3772011-08-30 16:51:11 -07005923 mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
Gilles Debunne77f18b02010-10-22 14:28:25 -07005924
Fabrice Di Megliod4c3b8e2011-11-09 18:04:07 -08005925 mBoring = mHintBoring = null;
5926
Gilles Debunne77f18b02010-10-22 14:28:25 -07005927 // Since it depends on the value of mLayout
Gilles Debunne2d373a12012-04-20 15:32:19 -07005928 if (mEditor != null) mEditor.prepareCursorControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005929 }
5930
5931 /**
5932 * Make a new Layout based on the already-measured size of the view,
5933 * on the assumption that it was measured correctly at some point.
5934 */
5935 private void assumeLayout() {
5936 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
5937
5938 if (width < 1) {
5939 width = 0;
5940 }
5941
5942 int physicalWidth = width;
5943
5944 if (mHorizontallyScrolling) {
Jeff Brown033a0012011-11-11 15:30:16 -08005945 width = VERY_WIDE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005946 }
5947
5948 makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
5949 physicalWidth, false);
5950 }
5951
Doug Feltc0ccf0c2011-06-23 16:13:18 -07005952 private Layout.Alignment getLayoutAlignment() {
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08005953 Layout.Alignment alignment;
5954 switch (getTextAlignment()) {
5955 case TEXT_ALIGNMENT_GRAVITY:
5956 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
5957 case Gravity.START:
5958 alignment = Layout.Alignment.ALIGN_NORMAL;
5959 break;
5960 case Gravity.END:
5961 alignment = Layout.Alignment.ALIGN_OPPOSITE;
5962 break;
5963 case Gravity.LEFT:
5964 alignment = Layout.Alignment.ALIGN_LEFT;
5965 break;
5966 case Gravity.RIGHT:
5967 alignment = Layout.Alignment.ALIGN_RIGHT;
5968 break;
5969 case Gravity.CENTER_HORIZONTAL:
5970 alignment = Layout.Alignment.ALIGN_CENTER;
5971 break;
5972 default:
5973 alignment = Layout.Alignment.ALIGN_NORMAL;
5974 break;
5975 }
5976 break;
5977 case TEXT_ALIGNMENT_TEXT_START:
5978 alignment = Layout.Alignment.ALIGN_NORMAL;
5979 break;
5980 case TEXT_ALIGNMENT_TEXT_END:
5981 alignment = Layout.Alignment.ALIGN_OPPOSITE;
5982 break;
5983 case TEXT_ALIGNMENT_CENTER:
5984 alignment = Layout.Alignment.ALIGN_CENTER;
5985 break;
5986 case TEXT_ALIGNMENT_VIEW_START:
5987 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
5988 Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
5989 break;
5990 case TEXT_ALIGNMENT_VIEW_END:
5991 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
5992 Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
5993 break;
5994 case TEXT_ALIGNMENT_INHERIT:
5995 // This should never happen as we have already resolved the text alignment
5996 // but better safe than sorry so we just fall through
5997 default:
5998 alignment = Layout.Alignment.ALIGN_NORMAL;
5999 break;
Doug Feltc0ccf0c2011-06-23 16:13:18 -07006000 }
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08006001 return alignment;
Doug Feltc0ccf0c2011-06-23 16:13:18 -07006002 }
6003
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006004 /**
6005 * The width passed in is now the desired layout width,
6006 * not the full view width with padding.
6007 * {@hide}
6008 */
Gilles Debunne287d6c62011-10-05 18:22:11 -07006009 protected void makeNewLayout(int wantWidth, int hintWidth,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006010 BoringLayout.Metrics boring,
6011 BoringLayout.Metrics hintBoring,
6012 int ellipsisWidth, boolean bringIntoView) {
6013 stopMarquee();
6014
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07006015 // Update "old" cached values
6016 mOldMaximum = mMaximum;
6017 mOldMaxMode = mMaxMode;
6018
Gilles Debunne83051b82012-02-24 20:01:13 -08006019 mHighlightPathBogus = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006020
Gilles Debunne287d6c62011-10-05 18:22:11 -07006021 if (wantWidth < 0) {
6022 wantWidth = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006023 }
6024 if (hintWidth < 0) {
6025 hintWidth = 0;
6026 }
6027
Doug Feltc0ccf0c2011-06-23 16:13:18 -07006028 Layout.Alignment alignment = getLayoutAlignment();
Raph Levienf5cf6c92013-04-12 11:31:31 -07006029 final boolean testDirChange = mSingleLine && mLayout != null &&
6030 (alignment == Layout.Alignment.ALIGN_NORMAL ||
6031 alignment == Layout.Alignment.ALIGN_OPPOSITE);
6032 int oldDir = 0;
6033 if (testDirChange) oldDir = mLayout.getParagraphDirection(0);
Gilles Debunne60e21862012-01-30 15:04:14 -08006034 boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
Adam Powell282e3772011-08-30 16:51:11 -07006035 final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE &&
6036 mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
6037 TruncateAt effectiveEllipsize = mEllipsize;
6038 if (mEllipsize == TruncateAt.MARQUEE &&
6039 mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
Fabrice Di Megliocb332642011-09-23 19:08:04 -07006040 effectiveEllipsize = TruncateAt.END_SMALL;
Adam Powell282e3772011-08-30 16:51:11 -07006041 }
Romain Guy4dc4f732009-06-19 15:16:40 -07006042
Doug Feltcb3791202011-07-07 11:57:48 -07006043 if (mTextDir == null) {
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07006044 mTextDir = getTextDirectionHeuristic();
Doug Feltcb3791202011-07-07 11:57:48 -07006045 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006046
Gilles Debunne287d6c62011-10-05 18:22:11 -07006047 mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
Adam Powell282e3772011-08-30 16:51:11 -07006048 effectiveEllipsize, effectiveEllipsize == mEllipsize);
6049 if (switchEllipsize) {
6050 TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE ?
6051 TruncateAt.END : TruncateAt.MARQUEE;
Gilles Debunne287d6c62011-10-05 18:22:11 -07006052 mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
Adam Powell282e3772011-08-30 16:51:11 -07006053 shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006054 }
6055
Romain Guy4dc4f732009-06-19 15:16:40 -07006056 shouldEllipsize = mEllipsize != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006057 mHintLayout = null;
6058
6059 if (mHint != null) {
Gilles Debunne287d6c62011-10-05 18:22:11 -07006060 if (shouldEllipsize) hintWidth = wantWidth;
Romain Guy4dc4f732009-06-19 15:16:40 -07006061
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006062 if (hintBoring == UNKNOWN_BORING) {
Doug Feltcb3791202011-07-07 11:57:48 -07006063 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006064 mHintBoring);
6065 if (hintBoring != null) {
6066 mHintBoring = hintBoring;
6067 }
6068 }
6069
6070 if (hintBoring != null) {
Romain Guy4dc4f732009-06-19 15:16:40 -07006071 if (hintBoring.width <= hintWidth &&
6072 (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006073 if (mSavedHintLayout != null) {
6074 mHintLayout = mSavedHintLayout.
6075 replaceOrMake(mHint, mTextPaint,
Romain Guy4dc4f732009-06-19 15:16:40 -07006076 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6077 hintBoring, mIncludePad);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006078 } else {
6079 mHintLayout = BoringLayout.make(mHint, mTextPaint,
Romain Guy4dc4f732009-06-19 15:16:40 -07006080 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6081 hintBoring, mIncludePad);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006082 }
6083
6084 mSavedHintLayout = (BoringLayout) mHintLayout;
Romain Guy4dc4f732009-06-19 15:16:40 -07006085 } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
6086 if (mSavedHintLayout != null) {
6087 mHintLayout = mSavedHintLayout.
6088 replaceOrMake(mHint, mTextPaint,
6089 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6090 hintBoring, mIncludePad, mEllipsize,
6091 ellipsisWidth);
6092 } else {
6093 mHintLayout = BoringLayout.make(mHint, mTextPaint,
6094 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6095 hintBoring, mIncludePad, mEllipsize,
6096 ellipsisWidth);
6097 }
6098 } else if (shouldEllipsize) {
6099 mHintLayout = new StaticLayout(mHint,
6100 0, mHint.length(),
Doug Feltcb3791202011-07-07 11:57:48 -07006101 mTextPaint, hintWidth, alignment, mTextDir, mSpacingMult,
Romain Guy4dc4f732009-06-19 15:16:40 -07006102 mSpacingAdd, mIncludePad, mEllipsize,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07006103 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006104 } else {
6105 mHintLayout = new StaticLayout(mHint, mTextPaint,
Doug Feltcb3791202011-07-07 11:57:48 -07006106 hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006107 mIncludePad);
6108 }
Romain Guy4dc4f732009-06-19 15:16:40 -07006109 } else if (shouldEllipsize) {
6110 mHintLayout = new StaticLayout(mHint,
6111 0, mHint.length(),
Doug Feltcb3791202011-07-07 11:57:48 -07006112 mTextPaint, hintWidth, alignment, mTextDir, mSpacingMult,
Romain Guy4dc4f732009-06-19 15:16:40 -07006113 mSpacingAdd, mIncludePad, mEllipsize,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07006114 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006115 } else {
6116 mHintLayout = new StaticLayout(mHint, mTextPaint,
Doug Feltcb3791202011-07-07 11:57:48 -07006117 hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006118 mIncludePad);
6119 }
6120 }
6121
Raph Levienf5cf6c92013-04-12 11:31:31 -07006122 if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006123 registerForPreDraw();
6124 }
6125
6126 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
Romain Guy939151f2009-04-08 14:22:40 -07006127 if (!compressText(ellipsisWidth)) {
6128 final int height = mLayoutParams.height;
6129 // If the size of the view does not depend on the size of the text, try to
6130 // start the marquee immediately
Romain Guy980a9382010-01-08 15:06:28 -08006131 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
Romain Guy939151f2009-04-08 14:22:40 -07006132 startMarquee();
6133 } else {
6134 // Defer the start of the marquee until we know our width (see setFrame())
6135 mRestartMarquee = true;
6136 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006137 }
6138 }
Gilles Debunnef788a9f2010-07-22 10:17:23 -07006139
6140 // CursorControllers need a non-null mLayout
Gilles Debunne2d373a12012-04-20 15:32:19 -07006141 if (mEditor != null) mEditor.prepareCursorControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006142 }
6143
Gilles Debunne287d6c62011-10-05 18:22:11 -07006144 private Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
Adam Powell282e3772011-08-30 16:51:11 -07006145 Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
6146 boolean useSaved) {
6147 Layout result = null;
6148 if (mText instanceof Spannable) {
Gilles Debunne287d6c62011-10-05 18:22:11 -07006149 result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth,
Adam Powell282e3772011-08-30 16:51:11 -07006150 alignment, mTextDir, mSpacingMult,
Gilles Debunne60e21862012-01-30 15:04:14 -08006151 mSpacingAdd, mIncludePad, getKeyListener() == null ? effectiveEllipsize : null,
Fabrice Di Meglioad0b0512011-10-04 17:21:26 -07006152 ellipsisWidth);
Adam Powell282e3772011-08-30 16:51:11 -07006153 } else {
6154 if (boring == UNKNOWN_BORING) {
6155 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
6156 if (boring != null) {
6157 mBoring = boring;
6158 }
6159 }
6160
6161 if (boring != null) {
Gilles Debunne287d6c62011-10-05 18:22:11 -07006162 if (boring.width <= wantWidth &&
Adam Powell282e3772011-08-30 16:51:11 -07006163 (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
6164 if (useSaved && mSavedLayout != null) {
6165 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006166 wantWidth, alignment, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006167 boring, mIncludePad);
6168 } else {
6169 result = BoringLayout.make(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006170 wantWidth, alignment, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006171 boring, mIncludePad);
6172 }
6173
6174 if (useSaved) {
6175 mSavedLayout = (BoringLayout) result;
6176 }
Gilles Debunne287d6c62011-10-05 18:22:11 -07006177 } else if (shouldEllipsize && boring.width <= wantWidth) {
Adam Powell282e3772011-08-30 16:51:11 -07006178 if (useSaved && mSavedLayout != null) {
6179 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006180 wantWidth, alignment, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006181 boring, mIncludePad, effectiveEllipsize,
6182 ellipsisWidth);
6183 } else {
6184 result = BoringLayout.make(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006185 wantWidth, alignment, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006186 boring, mIncludePad, effectiveEllipsize,
6187 ellipsisWidth);
6188 }
6189 } else if (shouldEllipsize) {
6190 result = new StaticLayout(mTransformed,
6191 0, mTransformed.length(),
Gilles Debunne287d6c62011-10-05 18:22:11 -07006192 mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult,
Adam Powell282e3772011-08-30 16:51:11 -07006193 mSpacingAdd, mIncludePad, effectiveEllipsize,
6194 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6195 } else {
6196 result = new StaticLayout(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006197 wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006198 mIncludePad);
6199 }
6200 } else if (shouldEllipsize) {
6201 result = new StaticLayout(mTransformed,
6202 0, mTransformed.length(),
Gilles Debunne287d6c62011-10-05 18:22:11 -07006203 mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult,
Adam Powell282e3772011-08-30 16:51:11 -07006204 mSpacingAdd, mIncludePad, effectiveEllipsize,
6205 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6206 } else {
6207 result = new StaticLayout(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006208 wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006209 mIncludePad);
6210 }
6211 }
6212 return result;
6213 }
6214
Romain Guy939151f2009-04-08 14:22:40 -07006215 private boolean compressText(float width) {
Romain Guy2bffd262010-09-12 17:40:02 -07006216 if (isHardwareAccelerated()) return false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07006217
Romain Guy3373ed62009-05-04 14:13:32 -07006218 // Only compress the text if it hasn't been compressed by the previous pass
6219 if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX &&
6220 mTextPaint.getTextScaleX() == 1.0f) {
Romain Guy939151f2009-04-08 14:22:40 -07006221 final float textWidth = mLayout.getLineWidth(0);
Romain Guy3373ed62009-05-04 14:13:32 -07006222 final float overflow = (textWidth + 1.0f - width) / width;
Romain Guy939151f2009-04-08 14:22:40 -07006223 if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
6224 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
6225 post(new Runnable() {
6226 public void run() {
6227 requestLayout();
6228 }
6229 });
6230 return true;
6231 }
6232 }
6233
6234 return false;
6235 }
6236
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006237 private static int desired(Layout layout) {
6238 int n = layout.getLineCount();
6239 CharSequence text = layout.getText();
6240 float max = 0;
6241
6242 // if any line was wrapped, we can't use it.
6243 // but it's ok for the last line not to have a newline
6244
6245 for (int i = 0; i < n - 1; i++) {
6246 if (text.charAt(layout.getLineEnd(i) - 1) != '\n')
6247 return -1;
6248 }
6249
6250 for (int i = 0; i < n; i++) {
6251 max = Math.max(max, layout.getLineWidth(i));
6252 }
6253
6254 return (int) FloatMath.ceil(max);
6255 }
6256
6257 /**
6258 * Set whether the TextView includes extra top and bottom padding to make
6259 * room for accents that go above the normal ascent and descent.
6260 * The default is true.
6261 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07006262 * @see #getIncludeFontPadding()
6263 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006264 * @attr ref android.R.styleable#TextView_includeFontPadding
6265 */
6266 public void setIncludeFontPadding(boolean includepad) {
Gilles Debunne22378292011-08-12 10:38:52 -07006267 if (mIncludePad != includepad) {
6268 mIncludePad = includepad;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006269
Gilles Debunne22378292011-08-12 10:38:52 -07006270 if (mLayout != null) {
6271 nullLayouts();
6272 requestLayout();
6273 invalidate();
6274 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006275 }
6276 }
6277
Gilles Debunnef03acef2012-04-30 19:26:19 -07006278 /**
6279 * Gets whether the TextView includes extra top and bottom padding to make
6280 * room for accents that go above the normal ascent and descent.
6281 *
6282 * @see #setIncludeFontPadding(boolean)
6283 *
6284 * @attr ref android.R.styleable#TextView_includeFontPadding
6285 */
6286 public boolean getIncludeFontPadding() {
6287 return mIncludePad;
6288 }
6289
Romain Guy4dc4f732009-06-19 15:16:40 -07006290 private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006291
6292 @Override
6293 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
6294 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
6295 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
6296 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
6297 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
6298
6299 int width;
6300 int height;
6301
6302 BoringLayout.Metrics boring = UNKNOWN_BORING;
6303 BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
6304
Doug Feltcb3791202011-07-07 11:57:48 -07006305 if (mTextDir == null) {
Fabrice Di Meglioa423f502013-05-14 13:20:32 -07006306 mTextDir = getTextDirectionHeuristic();
Doug Feltcb3791202011-07-07 11:57:48 -07006307 }
6308
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006309 int des = -1;
6310 boolean fromexisting = false;
6311
6312 if (widthMode == MeasureSpec.EXACTLY) {
6313 // Parent has told us how big to be. So be it.
6314 width = widthSize;
6315 } else {
6316 if (mLayout != null && mEllipsize == null) {
6317 des = desired(mLayout);
6318 }
6319
6320 if (des < 0) {
Doug Feltcb3791202011-07-07 11:57:48 -07006321 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006322 if (boring != null) {
6323 mBoring = boring;
6324 }
6325 } else {
6326 fromexisting = true;
6327 }
6328
6329 if (boring == null || boring == UNKNOWN_BORING) {
6330 if (des < 0) {
Romain Guy4dc4f732009-06-19 15:16:40 -07006331 des = (int) FloatMath.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006332 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006333 width = des;
6334 } else {
6335 width = boring.width;
6336 }
6337
6338 final Drawables dr = mDrawables;
6339 if (dr != null) {
6340 width = Math.max(width, dr.mDrawableWidthTop);
6341 width = Math.max(width, dr.mDrawableWidthBottom);
6342 }
6343
6344 if (mHint != null) {
6345 int hintDes = -1;
6346 int hintWidth;
6347
Romain Guy4dc4f732009-06-19 15:16:40 -07006348 if (mHintLayout != null && mEllipsize == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006349 hintDes = desired(mHintLayout);
6350 }
6351
6352 if (hintDes < 0) {
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006353 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006354 if (hintBoring != null) {
6355 mHintBoring = hintBoring;
6356 }
6357 }
6358
6359 if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
6360 if (hintDes < 0) {
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006361 hintDes = (int) FloatMath.ceil(Layout.getDesiredWidth(mHint, mTextPaint));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006362 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006363 hintWidth = hintDes;
6364 } else {
6365 hintWidth = hintBoring.width;
6366 }
6367
6368 if (hintWidth > width) {
6369 width = hintWidth;
6370 }
6371 }
6372
6373 width += getCompoundPaddingLeft() + getCompoundPaddingRight();
6374
6375 if (mMaxWidthMode == EMS) {
6376 width = Math.min(width, mMaxWidth * getLineHeight());
6377 } else {
6378 width = Math.min(width, mMaxWidth);
6379 }
6380
6381 if (mMinWidthMode == EMS) {
6382 width = Math.max(width, mMinWidth * getLineHeight());
6383 } else {
6384 width = Math.max(width, mMinWidth);
6385 }
6386
6387 // Check against our minimum width
6388 width = Math.max(width, getSuggestedMinimumWidth());
6389
6390 if (widthMode == MeasureSpec.AT_MOST) {
6391 width = Math.min(widthSize, width);
6392 }
6393 }
6394
6395 int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
6396 int unpaddedWidth = want;
Gilles Debunne9a80a652011-01-31 12:56:07 -08006397
Jeff Brown033a0012011-11-11 15:30:16 -08006398 if (mHorizontallyScrolling) want = VERY_WIDE;
Gilles Debunne9a80a652011-01-31 12:56:07 -08006399
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006400 int hintWant = want;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07006401 int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006402
6403 if (mLayout == null) {
6404 makeNewLayout(want, hintWant, boring, hintBoring,
Romain Guy4dc4f732009-06-19 15:16:40 -07006405 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006406 } else {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07006407 final boolean layoutChanged = (mLayout.getWidth() != want) ||
6408 (hintWidth != hintWant) ||
6409 (mLayout.getEllipsizedWidth() !=
6410 width - getCompoundPaddingLeft() - getCompoundPaddingRight());
6411
6412 final boolean widthChanged = (mHint == null) &&
6413 (mEllipsize == null) &&
6414 (want > mLayout.getWidth()) &&
6415 (mLayout instanceof BoringLayout || (fromexisting && des >= 0 && des <= want));
6416
6417 final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
6418
6419 if (layoutChanged || maximumChanged) {
6420 if (!maximumChanged && widthChanged) {
6421 mLayout.increaseWidthTo(want);
6422 } else {
6423 makeNewLayout(want, hintWant, boring, hintBoring,
6424 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
6425 }
6426 } else {
6427 // Nothing has changed
6428 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006429 }
6430
6431 if (heightMode == MeasureSpec.EXACTLY) {
6432 // Parent has told us how big to be. So be it.
6433 height = heightSize;
6434 mDesiredHeightAtMeasure = -1;
6435 } else {
6436 int desired = getDesiredHeight();
6437
6438 height = desired;
6439 mDesiredHeightAtMeasure = desired;
6440
6441 if (heightMode == MeasureSpec.AT_MOST) {
Christoffer Gurell1d05c7c2009-10-12 15:53:39 +02006442 height = Math.min(desired, heightSize);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006443 }
6444 }
6445
Romain Guy4dc4f732009-06-19 15:16:40 -07006446 int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006447 if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
Romain Guy4dc4f732009-06-19 15:16:40 -07006448 unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006449 }
6450
6451 /*
6452 * We didn't let makeNewLayout() register to bring the cursor into view,
6453 * so do it here if there is any possibility that it is needed.
6454 */
6455 if (mMovement != null ||
6456 mLayout.getWidth() > unpaddedWidth ||
6457 mLayout.getHeight() > unpaddedHeight) {
6458 registerForPreDraw();
6459 } else {
6460 scrollTo(0, 0);
6461 }
6462
6463 setMeasuredDimension(width, height);
6464 }
6465
6466 private int getDesiredHeight() {
Romain Guy4dc4f732009-06-19 15:16:40 -07006467 return Math.max(
6468 getDesiredHeight(mLayout, true),
6469 getDesiredHeight(mHintLayout, mEllipsize != null));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006470 }
6471
6472 private int getDesiredHeight(Layout layout, boolean cap) {
6473 if (layout == null) {
6474 return 0;
6475 }
6476
6477 int linecount = layout.getLineCount();
6478 int pad = getCompoundPaddingTop() + getCompoundPaddingBottom();
6479 int desired = layout.getLineTop(linecount);
6480
6481 final Drawables dr = mDrawables;
6482 if (dr != null) {
6483 desired = Math.max(desired, dr.mDrawableHeightLeft);
6484 desired = Math.max(desired, dr.mDrawableHeightRight);
6485 }
6486
6487 desired += pad;
6488
6489 if (mMaxMode == LINES) {
6490 /*
6491 * Don't cap the hint to a certain number of lines.
6492 * (Do cap it, though, if we have a maximum pixel height.)
6493 */
6494 if (cap) {
6495 if (linecount > mMaximum) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -08006496 desired = layout.getLineTop(mMaximum);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006497
6498 if (dr != null) {
6499 desired = Math.max(desired, dr.mDrawableHeightLeft);
6500 desired = Math.max(desired, dr.mDrawableHeightRight);
6501 }
6502
6503 desired += pad;
6504 linecount = mMaximum;
6505 }
6506 }
6507 } else {
6508 desired = Math.min(desired, mMaximum);
6509 }
6510
6511 if (mMinMode == LINES) {
6512 if (linecount < mMinimum) {
6513 desired += getLineHeight() * (mMinimum - linecount);
6514 }
6515 } else {
6516 desired = Math.max(desired, mMinimum);
6517 }
6518
6519 // Check against our minimum height
6520 desired = Math.max(desired, getSuggestedMinimumHeight());
6521
6522 return desired;
6523 }
6524
6525 /**
6526 * Check whether a change to the existing text layout requires a
6527 * new view layout.
6528 */
6529 private void checkForResize() {
6530 boolean sizeChanged = false;
6531
6532 if (mLayout != null) {
6533 // Check if our width changed
6534 if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
6535 sizeChanged = true;
6536 invalidate();
6537 }
6538
6539 // Check if our height changed
6540 if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
6541 int desiredHeight = getDesiredHeight();
6542
6543 if (desiredHeight != this.getHeight()) {
6544 sizeChanged = true;
6545 }
Romain Guy980a9382010-01-08 15:06:28 -08006546 } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006547 if (mDesiredHeightAtMeasure >= 0) {
6548 int desiredHeight = getDesiredHeight();
6549
6550 if (desiredHeight != mDesiredHeightAtMeasure) {
6551 sizeChanged = true;
6552 }
6553 }
6554 }
6555 }
6556
6557 if (sizeChanged) {
6558 requestLayout();
6559 // caller will have already invalidated
6560 }
6561 }
6562
6563 /**
6564 * Check whether entirely new text requires a new view layout
6565 * or merely a new text layout.
6566 */
6567 private void checkForRelayout() {
6568 // If we have a fixed width, we can just swap in a new text layout
6569 // if the text height stays the same or if the view height is fixed.
6570
6571 if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||
6572 (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&
6573 (mHint == null || mHintLayout != null) &&
6574 (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
6575 // Static width, so try making a new text layout.
6576
6577 int oldht = mLayout.getHeight();
6578 int want = mLayout.getWidth();
6579 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
6580
6581 /*
6582 * No need to bring the text into view, since the size is not
6583 * changing (unless we do the requestLayout(), in which case it
6584 * will happen at measure).
6585 */
6586 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
Romain Guye1e0dc82009-11-03 17:21:04 -08006587 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
6588 false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006589
Romain Guye1e0dc82009-11-03 17:21:04 -08006590 if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
6591 // In a fixed-height view, so use our new text layout.
6592 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
Romain Guy980a9382010-01-08 15:06:28 -08006593 mLayoutParams.height != LayoutParams.MATCH_PARENT) {
Romain Guye1e0dc82009-11-03 17:21:04 -08006594 invalidate();
6595 return;
6596 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006597
Romain Guye1e0dc82009-11-03 17:21:04 -08006598 // Dynamic height, but height has stayed the same,
6599 // so use our new text layout.
6600 if (mLayout.getHeight() == oldht &&
6601 (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
6602 invalidate();
6603 return;
6604 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006605 }
6606
6607 // We lose: the height has changed and we have a dynamic height.
6608 // Request a new view layout using our new text layout.
6609 requestLayout();
6610 invalidate();
6611 } else {
6612 // Dynamic width, so we have no choice but to request a new
6613 // view layout with a new text layout.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006614 nullLayouts();
6615 requestLayout();
6616 invalidate();
6617 }
6618 }
6619
Gilles Debunne954325e2012-01-25 11:57:06 -08006620 @Override
6621 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
6622 super.onLayout(changed, left, top, right, bottom);
Raph Levienf5c1a872012-10-15 17:22:26 -07006623 if (mDeferScroll >= 0) {
6624 int curs = mDeferScroll;
6625 mDeferScroll = -1;
Raph Levien8b179692012-10-16 14:32:47 -07006626 bringPointIntoView(Math.min(curs, mText.length()));
Raph Levienf5c1a872012-10-15 17:22:26 -07006627 }
Gilles Debunne954325e2012-01-25 11:57:06 -08006628 }
6629
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006630 private boolean isShowingHint() {
6631 return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint);
6632 }
6633
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006634 /**
6635 * Returns true if anything changed.
6636 */
6637 private boolean bringTextIntoView() {
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006638 Layout layout = isShowingHint() ? mHintLayout : mLayout;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006639 int line = 0;
6640 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006641 line = layout.getLineCount() - 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006642 }
6643
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006644 Layout.Alignment a = layout.getParagraphAlignment(line);
6645 int dir = layout.getParagraphDirection(line);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006646 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6647 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006648 int ht = layout.getHeight();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006649
6650 int scrollx, scrolly;
6651
Doug Felt25b9f422011-07-11 13:48:37 -07006652 // Convert to left, center, or right alignment.
6653 if (a == Layout.Alignment.ALIGN_NORMAL) {
6654 a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_LEFT :
6655 Layout.Alignment.ALIGN_RIGHT;
6656 } else if (a == Layout.Alignment.ALIGN_OPPOSITE){
6657 a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_RIGHT :
6658 Layout.Alignment.ALIGN_LEFT;
6659 }
6660
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006661 if (a == Layout.Alignment.ALIGN_CENTER) {
6662 /*
6663 * Keep centered if possible, or, if it is too wide to fit,
6664 * keep leading edge in view.
6665 */
6666
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006667 int left = (int) FloatMath.floor(layout.getLineLeft(line));
6668 int right = (int) FloatMath.ceil(layout.getLineRight(line));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006669
6670 if (right - left < hspace) {
6671 scrollx = (right + left) / 2 - hspace / 2;
6672 } else {
6673 if (dir < 0) {
6674 scrollx = right - hspace;
6675 } else {
6676 scrollx = left;
6677 }
6678 }
Fabrice Di Megliod2b5d1c2011-07-13 19:38:17 -07006679 } else if (a == Layout.Alignment.ALIGN_RIGHT) {
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006680 int right = (int) FloatMath.ceil(layout.getLineRight(line));
Doug Felt25b9f422011-07-11 13:48:37 -07006681 scrollx = right - hspace;
Fabrice Di Megliod2b5d1c2011-07-13 19:38:17 -07006682 } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006683 scrollx = (int) FloatMath.floor(layout.getLineLeft(line));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006684 }
6685
6686 if (ht < vspace) {
6687 scrolly = 0;
6688 } else {
6689 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
6690 scrolly = ht - vspace;
6691 } else {
6692 scrolly = 0;
6693 }
6694 }
6695
6696 if (scrollx != mScrollX || scrolly != mScrollY) {
6697 scrollTo(scrollx, scrolly);
6698 return true;
6699 } else {
6700 return false;
6701 }
6702 }
6703
6704 /**
6705 * Move the point, specified by the offset, into the view if it is needed.
6706 * This has to be called after layout. Returns true if anything changed.
6707 */
6708 public boolean bringPointIntoView(int offset) {
Raph Levienf5c1a872012-10-15 17:22:26 -07006709 if (isLayoutRequested()) {
6710 mDeferScroll = offset;
6711 return false;
6712 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006713 boolean changed = false;
6714
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006715 Layout layout = isShowingHint() ? mHintLayout: mLayout;
Gilles Debunne176ee3d2011-07-16 13:28:41 -07006716
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006717 if (layout == null) return changed;
6718
6719 int line = layout.getLineForOffset(offset);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006720
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006721 int grav;
6722
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006723 switch (layout.getParagraphAlignment(line)) {
Doug Felt25b9f422011-07-11 13:48:37 -07006724 case ALIGN_LEFT:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006725 grav = 1;
6726 break;
Doug Felt25b9f422011-07-11 13:48:37 -07006727 case ALIGN_RIGHT:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006728 grav = -1;
6729 break;
Doug Felt25b9f422011-07-11 13:48:37 -07006730 case ALIGN_NORMAL:
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006731 grav = layout.getParagraphDirection(line);
Doug Felt25b9f422011-07-11 13:48:37 -07006732 break;
6733 case ALIGN_OPPOSITE:
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006734 grav = -layout.getParagraphDirection(line);
Doug Felt25b9f422011-07-11 13:48:37 -07006735 break;
6736 case ALIGN_CENTER:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006737 default:
6738 grav = 0;
Doug Felt25b9f422011-07-11 13:48:37 -07006739 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006740 }
6741
Raph Levienafe8e9b2012-12-19 16:09:32 -08006742 // We only want to clamp the cursor to fit within the layout width
6743 // in left-to-right modes, because in a right to left alignment,
6744 // we want to scroll to keep the line-right on the screen, as other
6745 // lines are likely to have text flush with the right margin, which
6746 // we want to keep visible.
6747 // A better long-term solution would probably be to measure both
6748 // the full line and a blank-trimmed version, and, for example, use
6749 // the latter measurement for centering and right alignment, but for
6750 // the time being we only implement the cursor clamping in left to
6751 // right where it is most likely to be annoying.
6752 final boolean clamped = grav > 0;
6753 // FIXME: Is it okay to truncate this, or should we round?
6754 final int x = (int)layout.getPrimaryHorizontal(offset, clamped);
6755 final int top = layout.getLineTop(line);
6756 final int bottom = layout.getLineTop(line + 1);
6757
6758 int left = (int) FloatMath.floor(layout.getLineLeft(line));
6759 int right = (int) FloatMath.ceil(layout.getLineRight(line));
6760 int ht = layout.getHeight();
6761
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006762 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6763 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
Raph Levienafe8e9b2012-12-19 16:09:32 -08006764 if (!mHorizontallyScrolling && right - left > hspace && right > x) {
6765 // If cursor has been clamped, make sure we don't scroll.
6766 right = Math.max(x, left + hspace);
6767 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006768
6769 int hslack = (bottom - top) / 2;
6770 int vslack = hslack;
6771
6772 if (vslack > vspace / 4)
6773 vslack = vspace / 4;
6774 if (hslack > hspace / 4)
6775 hslack = hspace / 4;
6776
6777 int hs = mScrollX;
6778 int vs = mScrollY;
6779
6780 if (top - vs < vslack)
6781 vs = top - vslack;
6782 if (bottom - vs > vspace - vslack)
6783 vs = bottom - (vspace - vslack);
6784 if (ht - vs < vspace)
6785 vs = ht - vspace;
6786 if (0 - vs > 0)
6787 vs = 0;
6788
6789 if (grav != 0) {
6790 if (x - hs < hslack) {
6791 hs = x - hslack;
6792 }
6793 if (x - hs > hspace - hslack) {
6794 hs = x - (hspace - hslack);
6795 }
6796 }
6797
6798 if (grav < 0) {
6799 if (left - hs > 0)
6800 hs = left;
6801 if (right - hs < hspace)
6802 hs = right - hspace;
6803 } else if (grav > 0) {
6804 if (right - hs < hspace)
6805 hs = right - hspace;
6806 if (left - hs > 0)
6807 hs = left;
6808 } else /* grav == 0 */ {
6809 if (right - left <= hspace) {
6810 /*
6811 * If the entire text fits, center it exactly.
6812 */
6813 hs = left - (hspace - (right - left)) / 2;
6814 } else if (x > right - hslack) {
6815 /*
6816 * If we are near the right edge, keep the right edge
6817 * at the edge of the view.
6818 */
6819 hs = right - hspace;
6820 } else if (x < left + hslack) {
6821 /*
6822 * If we are near the left edge, keep the left edge
6823 * at the edge of the view.
6824 */
6825 hs = left;
6826 } else if (left > hs) {
6827 /*
6828 * Is there whitespace visible at the left? Fix it if so.
6829 */
6830 hs = left;
6831 } else if (right < hs + hspace) {
6832 /*
6833 * Is there whitespace visible at the right? Fix it if so.
6834 */
6835 hs = right - hspace;
6836 } else {
6837 /*
6838 * Otherwise, float as needed.
6839 */
6840 if (x - hs < hslack) {
6841 hs = x - hslack;
6842 }
6843 if (x - hs > hspace - hslack) {
6844 hs = x - (hspace - hslack);
6845 }
6846 }
6847 }
6848
6849 if (hs != mScrollX || vs != mScrollY) {
6850 if (mScroller == null) {
6851 scrollTo(hs, vs);
6852 } else {
6853 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
6854 int dx = hs - mScrollX;
6855 int dy = vs - mScrollY;
6856
6857 if (duration > ANIMATED_SCROLL_GAP) {
6858 mScroller.startScroll(mScrollX, mScrollY, dx, dy);
Mike Cleronf116bf82009-09-27 19:14:12 -07006859 awakenScrollBars(mScroller.getDuration());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006860 invalidate();
6861 } else {
6862 if (!mScroller.isFinished()) {
6863 mScroller.abortAnimation();
6864 }
6865
6866 scrollBy(dx, dy);
6867 }
6868
6869 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
6870 }
6871
6872 changed = true;
6873 }
6874
6875 if (isFocused()) {
Gilles Debunne716dbf62011-03-07 18:12:10 -08006876 // This offsets because getInterestingRect() is in terms of viewport coordinates, but
6877 // requestRectangleOnScreen() is in terms of content coordinates.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006878
Dianne Hackborn70a3f672011-08-08 14:32:41 -07006879 // The offsets here are to ensure the rectangle we are using is
6880 // within our view bounds, in case the cursor is on the far left
6881 // or right. If it isn't withing the bounds, then this request
6882 // will be ignored.
Gilles Debunne60e21862012-01-30 15:04:14 -08006883 if (mTempRect == null) mTempRect = new Rect();
Dianne Hackborn70a3f672011-08-08 14:32:41 -07006884 mTempRect.set(x - 2, top, x + 2, bottom);
Gilles Debunne716dbf62011-03-07 18:12:10 -08006885 getInterestingRect(mTempRect, line);
6886 mTempRect.offset(mScrollX, mScrollY);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006887
Gilles Debunne716dbf62011-03-07 18:12:10 -08006888 if (requestRectangleOnScreen(mTempRect)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006889 changed = true;
6890 }
6891 }
6892
6893 return changed;
6894 }
6895
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006896 /**
6897 * Move the cursor, if needed, so that it is at an offset that is visible
6898 * to the user. This will not move the cursor if it represents more than
6899 * one character (a selection range). This will only work if the
6900 * TextView contains spannable text; otherwise it will do nothing.
Gilles Debunnebbb5d6e2010-06-21 16:21:51 -07006901 *
Gilles Debunne57f4e5b2010-06-21 16:21:51 -07006902 * @return True if the cursor was actually moved, false otherwise.
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006903 */
6904 public boolean moveCursorToVisibleOffset() {
6905 if (!(mText instanceof Spannable)) {
6906 return false;
6907 }
Gilles Debunne05336272010-07-09 20:13:45 -07006908 int start = getSelectionStart();
6909 int end = getSelectionEnd();
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006910 if (start != end) {
6911 return false;
6912 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006913
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006914 // First: make sure the line is visible on screen:
Gilles Debunne2d373a12012-04-20 15:32:19 -07006915
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006916 int line = mLayout.getLineForOffset(start);
6917
6918 final int top = mLayout.getLineTop(line);
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07006919 final int bottom = mLayout.getLineTop(line + 1);
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006920 final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
6921 int vslack = (bottom - top) / 2;
6922 if (vslack > vspace / 4)
6923 vslack = vspace / 4;
6924 final int vs = mScrollY;
6925
6926 if (top < (vs+vslack)) {
6927 line = mLayout.getLineForVertical(vs+vslack+(bottom-top));
6928 } else if (bottom > (vspace+vs-vslack)) {
6929 line = mLayout.getLineForVertical(vspace+vs-vslack-(bottom-top));
6930 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006931
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006932 // Next: make sure the character is visible on screen:
Gilles Debunne2d373a12012-04-20 15:32:19 -07006933
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006934 final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6935 final int hs = mScrollX;
6936 final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
6937 final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs);
Gilles Debunne2d373a12012-04-20 15:32:19 -07006938
Doug Feltc982f602010-05-25 11:51:40 -07006939 // line might contain bidirectional text
6940 final int lowChar = leftChar < rightChar ? leftChar : rightChar;
6941 final int highChar = leftChar > rightChar ? leftChar : rightChar;
6942
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006943 int newStart = start;
Doug Feltc982f602010-05-25 11:51:40 -07006944 if (newStart < lowChar) {
6945 newStart = lowChar;
6946 } else if (newStart > highChar) {
6947 newStart = highChar;
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006948 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006949
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006950 if (newStart != start) {
6951 Selection.setSelection((Spannable)mText, newStart);
6952 return true;
6953 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006954
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006955 return false;
6956 }
6957
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006958 @Override
6959 public void computeScroll() {
6960 if (mScroller != null) {
6961 if (mScroller.computeScrollOffset()) {
6962 mScrollX = mScroller.getCurrX();
6963 mScrollY = mScroller.getCurrY();
Romain Guy0fd89bf2011-01-26 15:41:30 -08006964 invalidateParentCaches();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006965 postInvalidate(); // So we draw again
6966 }
6967 }
6968 }
6969
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07006970 private void getInterestingRect(Rect r, int line) {
6971 convertFromViewportToContentCoordinates(r);
6972
6973 // Rectangle can can be expanded on first and last line to take
6974 // padding into account.
6975 // TODO Take left/right padding into account too?
6976 if (line == 0) r.top -= getExtendedPaddingTop();
6977 if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
6978 }
6979
6980 private void convertFromViewportToContentCoordinates(Rect r) {
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07006981 final int horizontalOffset = viewportToContentHorizontalOffset();
6982 r.left += horizontalOffset;
6983 r.right += horizontalOffset;
6984
6985 final int verticalOffset = viewportToContentVerticalOffset();
6986 r.top += verticalOffset;
6987 r.bottom += verticalOffset;
6988 }
6989
Gilles Debunned88876a2012-03-16 17:34:04 -07006990 int viewportToContentHorizontalOffset() {
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07006991 return getCompoundPaddingLeft() - mScrollX;
6992 }
6993
Gilles Debunned88876a2012-03-16 17:34:04 -07006994 int viewportToContentVerticalOffset() {
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07006995 int offset = getExtendedPaddingTop() - mScrollY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006996 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07006997 offset += getVerticalOffset(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006998 }
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07006999 return offset;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007000 }
7001
7002 @Override
7003 public void debug(int depth) {
7004 super.debug(depth);
7005
7006 String output = debugIndent(depth);
7007 output += "frame={" + mLeft + ", " + mTop + ", " + mRight
7008 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
7009 + "} ";
7010
7011 if (mText != null) {
7012
7013 output += "mText=\"" + mText + "\" ";
7014 if (mLayout != null) {
7015 output += "mLayout width=" + mLayout.getWidth()
7016 + " height=" + mLayout.getHeight();
7017 }
7018 } else {
7019 output += "mText=NULL";
7020 }
7021 Log.d(VIEW_LOG_TAG, output);
7022 }
7023
7024 /**
7025 * Convenience for {@link Selection#getSelectionStart}.
7026 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07007027 @ViewDebug.ExportedProperty(category = "text")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007028 public int getSelectionStart() {
7029 return Selection.getSelectionStart(getText());
7030 }
7031
7032 /**
7033 * Convenience for {@link Selection#getSelectionEnd}.
7034 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07007035 @ViewDebug.ExportedProperty(category = "text")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007036 public int getSelectionEnd() {
7037 return Selection.getSelectionEnd(getText());
7038 }
7039
7040 /**
7041 * Return true iff there is a selection inside this text view.
7042 */
7043 public boolean hasSelection() {
Gilles Debunne03789e82010-09-07 19:07:17 -07007044 final int selectionStart = getSelectionStart();
7045 final int selectionEnd = getSelectionEnd();
7046
7047 return selectionStart >= 0 && selectionStart != selectionEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007048 }
7049
7050 /**
7051 * Sets the properties of this field (lines, horizontally scrolling,
7052 * transformation method) to be for a single-line input.
7053 *
7054 * @attr ref android.R.styleable#TextView_singleLine
7055 */
7056 public void setSingleLine() {
7057 setSingleLine(true);
7058 }
7059
7060 /**
Adam Powell7f8f79a2011-07-07 18:35:54 -07007061 * Sets the properties of this field to transform input to ALL CAPS
7062 * display. This may use a "small caps" formatting if available.
7063 * This setting will be ignored if this field is editable or selectable.
7064 *
7065 * This call replaces the current transformation method. Disabling this
7066 * will not necessarily restore the previous behavior from before this
7067 * was enabled.
7068 *
7069 * @see #setTransformationMethod(TransformationMethod)
7070 * @attr ref android.R.styleable#TextView_textAllCaps
7071 */
7072 public void setAllCaps(boolean allCaps) {
7073 if (allCaps) {
7074 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
7075 } else {
7076 setTransformationMethod(null);
7077 }
7078 }
7079
7080 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08007081 * If true, sets the properties of this field (number of lines, horizontally scrolling,
7082 * transformation method) to be for a single-line input; if false, restores these to the default
7083 * conditions.
7084 *
7085 * Note that the default conditions are not necessarily those that were in effect prior this
7086 * method, and you may want to reset these properties to your custom values.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007087 *
7088 * @attr ref android.R.styleable#TextView_singleLine
7089 */
7090 @android.view.RemotableViewMethod
7091 public void setSingleLine(boolean singleLine) {
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08007092 // Could be used, but may break backward compatibility.
7093 // if (mSingleLine == singleLine) return;
Gilles Debunned7483bf2010-11-10 10:47:45 -08007094 setInputTypeSingleLine(singleLine);
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08007095 applySingleLine(singleLine, true, true);
Gilles Debunned7483bf2010-11-10 10:47:45 -08007096 }
7097
7098 /**
7099 * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
7100 * @param singleLine
7101 */
7102 private void setInputTypeSingleLine(boolean singleLine) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007103 if (mEditor != null &&
7104 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007105 if (singleLine) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007106 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007107 } else {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007108 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007109 }
7110 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007111 }
7112
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08007113 private void applySingleLine(boolean singleLine, boolean applyTransformation,
7114 boolean changeMaxLines) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007115 mSingleLine = singleLine;
7116 if (singleLine) {
7117 setLines(1);
7118 setHorizontallyScrolling(true);
7119 if (applyTransformation) {
Gilles Debunne91a08cf2010-11-08 17:34:49 -08007120 setTransformationMethod(SingleLineTransformationMethod.getInstance());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007121 }
7122 } else {
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08007123 if (changeMaxLines) {
7124 setMaxLines(Integer.MAX_VALUE);
7125 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007126 setHorizontallyScrolling(false);
7127 if (applyTransformation) {
7128 setTransformationMethod(null);
7129 }
7130 }
7131 }
Gilles Debunneb2316962010-12-21 17:32:43 -08007132
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007133 /**
7134 * Causes words in the text that are longer than the view is wide
7135 * to be ellipsized instead of broken in the middle. You may also
7136 * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
Kenny Roote855d132009-06-11 11:00:42 -05007137 * to constrain the text to a single line. Use <code>null</code>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007138 * to turn off ellipsizing.
7139 *
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07007140 * If {@link #setMaxLines} has been used to set two or more lines,
Gilles Debunne6435a562011-08-04 21:22:30 -07007141 * {@link android.text.TextUtils.TruncateAt#END} and
7142 * {@link android.text.TextUtils.TruncateAt#MARQUEE}* are only supported
7143 * (other ellipsizing types will not do anything).
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07007144 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007145 * @attr ref android.R.styleable#TextView_ellipsize
7146 */
7147 public void setEllipsize(TextUtils.TruncateAt where) {
Gilles Debunne22378292011-08-12 10:38:52 -07007148 // TruncateAt is an enum. != comparison is ok between these singleton objects.
7149 if (mEllipsize != where) {
7150 mEllipsize = where;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007151
Gilles Debunne22378292011-08-12 10:38:52 -07007152 if (mLayout != null) {
7153 nullLayouts();
7154 requestLayout();
7155 invalidate();
7156 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007157 }
7158 }
7159
7160 /**
7161 * Sets how many times to repeat the marquee animation. Only applied if the
7162 * TextView has marquee enabled. Set to -1 to repeat indefinitely.
7163 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07007164 * @see #getMarqueeRepeatLimit()
7165 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007166 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
7167 */
7168 public void setMarqueeRepeatLimit(int marqueeLimit) {
7169 mMarqueeRepeatLimit = marqueeLimit;
7170 }
7171
7172 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07007173 * Gets the number of times the marquee animation is repeated. Only meaningful if the
7174 * TextView has marquee enabled.
7175 *
7176 * @return the number of times the marquee animation is repeated. -1 if the animation
7177 * repeats indefinitely
7178 *
7179 * @see #setMarqueeRepeatLimit(int)
7180 *
7181 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
7182 */
7183 public int getMarqueeRepeatLimit() {
7184 return mMarqueeRepeatLimit;
7185 }
7186
7187 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007188 * Returns where, if anywhere, words that are longer than the view
7189 * is wide should be ellipsized.
7190 */
7191 @ViewDebug.ExportedProperty
7192 public TextUtils.TruncateAt getEllipsize() {
7193 return mEllipsize;
7194 }
7195
7196 /**
7197 * Set the TextView so that when it takes focus, all the text is
7198 * selected.
7199 *
7200 * @attr ref android.R.styleable#TextView_selectAllOnFocus
7201 */
7202 @android.view.RemotableViewMethod
7203 public void setSelectAllOnFocus(boolean selectAllOnFocus) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07007204 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07007205 mEditor.mSelectAllOnFocus = selectAllOnFocus;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007206
7207 if (selectAllOnFocus && !(mText instanceof Spannable)) {
7208 setText(mText, BufferType.SPANNABLE);
7209 }
7210 }
7211
7212 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07007213 * Set whether the cursor is visible. The default is true. Note that this property only
7214 * makes sense for editable TextView.
7215 *
7216 * @see #isCursorVisible()
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007217 *
7218 * @attr ref android.R.styleable#TextView_cursorVisible
7219 */
7220 @android.view.RemotableViewMethod
7221 public void setCursorVisible(boolean visible) {
Gilles Debunne60e21862012-01-30 15:04:14 -08007222 if (visible && mEditor == null) return; // visible is the default value with no edit data
Gilles Debunne5fae9962012-05-08 14:53:20 -07007223 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07007224 if (mEditor.mCursorVisible != visible) {
7225 mEditor.mCursorVisible = visible;
Gilles Debunne3d010062011-02-18 14:16:41 -08007226 invalidate();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007227
Gilles Debunne2d373a12012-04-20 15:32:19 -07007228 mEditor.makeBlink();
Gilles Debunnef788a9f2010-07-22 10:17:23 -07007229
Gilles Debunne3d010062011-02-18 14:16:41 -08007230 // InsertionPointCursorController depends on mCursorVisible
Gilles Debunne2d373a12012-04-20 15:32:19 -07007231 mEditor.prepareCursorControllers();
Gilles Debunne3d010062011-02-18 14:16:41 -08007232 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007233 }
7234
Gilles Debunnef03acef2012-04-30 19:26:19 -07007235 /**
7236 * @return whether or not the cursor is visible (assuming this TextView is editable)
7237 *
7238 * @see #setCursorVisible(boolean)
7239 *
7240 * @attr ref android.R.styleable#TextView_cursorVisible
7241 */
7242 public boolean isCursorVisible() {
7243 // true is the default value
7244 return mEditor == null ? true : mEditor.mCursorVisible;
7245 }
7246
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007247 private boolean canMarquee() {
7248 int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight());
Adam Powell282e3772011-08-30 16:51:11 -07007249 return width > 0 && (mLayout.getLineWidth(0) > width ||
7250 (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null &&
7251 mSavedMarqueeModeLayout.getLineWidth(0) > width));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007252 }
7253
7254 private void startMarquee() {
Romain Guy4dc4f732009-06-19 15:16:40 -07007255 // Do not ellipsize EditText
Gilles Debunne60e21862012-01-30 15:04:14 -08007256 if (getKeyListener() != null) return;
Romain Guy4dc4f732009-06-19 15:16:40 -07007257
Romain Guy939151f2009-04-08 14:22:40 -07007258 if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
7259 return;
7260 }
7261
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007262 if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) &&
7263 getLineCount() == 1 && canMarquee()) {
Romain Guy939151f2009-04-08 14:22:40 -07007264
Adam Powell282e3772011-08-30 16:51:11 -07007265 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
7266 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
7267 final Layout tmp = mLayout;
7268 mLayout = mSavedMarqueeModeLayout;
7269 mSavedMarqueeModeLayout = tmp;
7270 setHorizontalFadingEdgeEnabled(true);
7271 requestLayout();
7272 invalidate();
7273 }
7274
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007275 if (mMarquee == null) mMarquee = new Marquee(this);
7276 mMarquee.start(mMarqueeRepeatLimit);
7277 }
7278 }
7279
7280 private void stopMarquee() {
7281 if (mMarquee != null && !mMarquee.isStopped()) {
7282 mMarquee.stop();
7283 }
Adam Powell282e3772011-08-30 16:51:11 -07007284
7285 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
7286 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
7287 final Layout tmp = mSavedMarqueeModeLayout;
7288 mSavedMarqueeModeLayout = mLayout;
7289 mLayout = tmp;
7290 setHorizontalFadingEdgeEnabled(false);
7291 requestLayout();
7292 invalidate();
7293 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007294 }
7295
7296 private void startStopMarquee(boolean start) {
7297 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7298 if (start) {
7299 startMarquee();
7300 } else {
7301 stopMarquee();
7302 }
7303 }
7304 }
7305
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007306 /**
Gilles Debunne4469e602011-03-09 14:38:04 -08007307 * This method is called when the text is changed, in case any subclasses
7308 * would like to know.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007309 *
Gilles Debunne4469e602011-03-09 14:38:04 -08007310 * Within <code>text</code>, the <code>lengthAfter</code> characters
7311 * beginning at <code>start</code> have just replaced old text that had
7312 * length <code>lengthBefore</code>. It is an error to attempt to make
7313 * changes to <code>text</code> from this callback.
7314 *
7315 * @param text The text the TextView is displaying
7316 * @param start The offset of the start of the range of the text that was
7317 * modified
7318 * @param lengthBefore The length of the former text that has been replaced
7319 * @param lengthAfter The length of the replacement modified text
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007320 */
Gilles Debunne4469e602011-03-09 14:38:04 -08007321 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
Gilles Debunne6435a562011-08-04 21:22:30 -07007322 // intentionally empty, template pattern method can be overridden by subclasses
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007323 }
7324
7325 /**
7326 * This method is called when the selection has changed, in case any
7327 * subclasses would like to know.
Gilles Debunne2d373a12012-04-20 15:32:19 -07007328 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007329 * @param selStart The new selection start location.
7330 * @param selEnd The new selection end location.
7331 */
7332 protected void onSelectionChanged(int selStart, int selEnd) {
Svetoslav Ganova0156172011-06-26 17:55:44 -07007333 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007334 }
Svetoslav Ganova0156172011-06-26 17:55:44 -07007335
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007336 /**
7337 * Adds a TextWatcher to the list of those whose methods are called
7338 * whenever this TextView's text changes.
7339 * <p>
7340 * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
7341 * not called after {@link #setText} calls. Now, doing {@link #setText}
7342 * if there are any text changed listeners forces the buffer type to
7343 * Editable if it would not otherwise be and does call this method.
7344 */
7345 public void addTextChangedListener(TextWatcher watcher) {
7346 if (mListeners == null) {
7347 mListeners = new ArrayList<TextWatcher>();
7348 }
7349
7350 mListeners.add(watcher);
7351 }
7352
7353 /**
7354 * Removes the specified TextWatcher from the list of those whose
7355 * methods are called
7356 * whenever this TextView's text changes.
7357 */
7358 public void removeTextChangedListener(TextWatcher watcher) {
7359 if (mListeners != null) {
7360 int i = mListeners.indexOf(watcher);
7361
7362 if (i >= 0) {
7363 mListeners.remove(i);
7364 }
7365 }
7366 }
7367
Gilles Debunne6435a562011-08-04 21:22:30 -07007368 private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007369 if (mListeners != null) {
7370 final ArrayList<TextWatcher> list = mListeners;
7371 final int count = list.size();
7372 for (int i = 0; i < count; i++) {
7373 list.get(i).beforeTextChanged(text, start, before, after);
7374 }
7375 }
Gilles Debunne6435a562011-08-04 21:22:30 -07007376
7377 // The spans that are inside or intersect the modified region no longer make sense
Satoshi Kataokad7429c12013-06-05 16:30:23 +09007378 removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class);
7379 removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class);
Gilles Debunne6435a562011-08-04 21:22:30 -07007380 }
7381
7382 // Removes all spans that are inside or actually overlap the start..end range
Satoshi Kataokad7429c12013-06-05 16:30:23 +09007383 private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) {
Gilles Debunne6435a562011-08-04 21:22:30 -07007384 if (!(mText instanceof Editable)) return;
7385 Editable text = (Editable) mText;
7386
7387 T[] spans = text.getSpans(start, end, type);
7388 final int length = spans.length;
7389 for (int i = 0; i < length; i++) {
Satoshi Kataokad7429c12013-06-05 16:30:23 +09007390 final int spanStart = text.getSpanStart(spans[i]);
7391 final int spanEnd = text.getSpanEnd(spans[i]);
7392 if (spanEnd == start || spanStart == end) break;
Gilles Debunne6435a562011-08-04 21:22:30 -07007393 text.removeSpan(spans[i]);
7394 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007395 }
7396
Satoshi Kataokad7429c12013-06-05 16:30:23 +09007397 void removeAdjacentSuggestionSpans(final int pos) {
7398 if (!(mText instanceof Editable)) return;
7399 final Editable text = (Editable) mText;
7400
7401 final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class);
7402 final int length = spans.length;
7403 for (int i = 0; i < length; i++) {
7404 final int spanStart = text.getSpanStart(spans[i]);
7405 final int spanEnd = text.getSpanEnd(spans[i]);
7406 if (spanEnd == pos || spanStart == pos) {
7407 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) {
7408 text.removeSpan(spans[i]);
7409 }
7410 }
7411 }
7412 }
7413
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007414 /**
7415 * Not private so it can be called from an inner class without going
7416 * through a thunk.
7417 */
Gilles Debunne6435a562011-08-04 21:22:30 -07007418 void sendOnTextChanged(CharSequence text, int start, int before, int after) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007419 if (mListeners != null) {
7420 final ArrayList<TextWatcher> list = mListeners;
7421 final int count = list.size();
7422 for (int i = 0; i < count; i++) {
7423 list.get(i).onTextChanged(text, start, before, after);
7424 }
7425 }
Gilles Debunne1a22db22011-11-20 22:13:21 +01007426
Gilles Debunne2d373a12012-04-20 15:32:19 -07007427 if (mEditor != null) mEditor.sendOnTextChanged(start, after);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007428 }
7429
7430 /**
7431 * Not private so it can be called from an inner class without going
7432 * through a thunk.
7433 */
7434 void sendAfterTextChanged(Editable text) {
7435 if (mListeners != null) {
7436 final ArrayList<TextWatcher> list = mListeners;
7437 final int count = list.size();
7438 for (int i = 0; i < count; i++) {
7439 list.get(i).afterTextChanged(text);
7440 }
7441 }
7442 }
7443
Gilles Debunned88876a2012-03-16 17:34:04 -07007444 void updateAfterEdit() {
7445 invalidate();
7446 int curs = getSelectionStart();
7447
7448 if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
7449 registerForPreDraw();
7450 }
7451
Raph Levienf5c1a872012-10-15 17:22:26 -07007452 checkForResize();
7453
Gilles Debunned88876a2012-03-16 17:34:04 -07007454 if (curs >= 0) {
7455 mHighlightPathBogus = true;
Gilles Debunne2d373a12012-04-20 15:32:19 -07007456 if (mEditor != null) mEditor.makeBlink();
Gilles Debunned88876a2012-03-16 17:34:04 -07007457 bringPointIntoView(curs);
7458 }
Gilles Debunned88876a2012-03-16 17:34:04 -07007459 }
7460
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007461 /**
7462 * Not private so it can be called from an inner class without going
7463 * through a thunk.
7464 */
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08007465 void handleTextChanged(CharSequence buffer, int start, int before, int after) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007466 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007467 if (ims == null || ims.mBatchEditNesting == 0) {
7468 updateAfterEdit();
7469 }
7470 if (ims != null) {
7471 ims.mContentChanged = true;
7472 if (ims.mChangedStart < 0) {
7473 ims.mChangedStart = start;
7474 ims.mChangedEnd = start+before;
7475 } else {
Viktor Yakovel964be412010-02-17 08:35:57 +01007476 ims.mChangedStart = Math.min(ims.mChangedStart, start);
7477 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007478 }
7479 ims.mChangedDelta += after-before;
7480 }
Gilles Debunne186aaf92011-09-16 14:26:12 -07007481
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007482 sendOnTextChanged(buffer, start, before, after);
7483 onTextChanged(buffer, start, before, after);
7484 }
Gilles Debunne60e21862012-01-30 15:04:14 -08007485
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007486 /**
7487 * Not private so it can be called from an inner class without going
7488 * through a thunk.
7489 */
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08007490 void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007491 // XXX Make the start and end move together if this ends up
7492 // spending too much time invalidating.
7493
7494 boolean selChanged = false;
7495 int newSelStart=-1, newSelEnd=-1;
Gilles Debunne60e21862012-01-30 15:04:14 -08007496
Gilles Debunne2d373a12012-04-20 15:32:19 -07007497 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
Gilles Debunne60e21862012-01-30 15:04:14 -08007498
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007499 if (what == Selection.SELECTION_END) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007500 selChanged = true;
7501 newSelEnd = newStart;
7502
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007503 if (oldStart >= 0 || newStart >= 0) {
7504 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
Raph Levienf5c1a872012-10-15 17:22:26 -07007505 checkForResize();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007506 registerForPreDraw();
Gilles Debunne2d373a12012-04-20 15:32:19 -07007507 if (mEditor != null) mEditor.makeBlink();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007508 }
7509 }
7510
7511 if (what == Selection.SELECTION_START) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007512 selChanged = true;
7513 newSelStart = newStart;
7514
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007515 if (oldStart >= 0 || newStart >= 0) {
7516 int end = Selection.getSelectionEnd(buf);
7517 invalidateCursor(end, oldStart, newStart);
7518 }
7519 }
7520
7521 if (selChanged) {
Gilles Debunne83051b82012-02-24 20:01:13 -08007522 mHighlightPathBogus = true;
Gilles Debunne2d373a12012-04-20 15:32:19 -07007523 if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
Gilles Debunne60e21862012-01-30 15:04:14 -08007524
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007525 if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) {
7526 if (newSelStart < 0) {
7527 newSelStart = Selection.getSelectionStart(buf);
7528 }
7529 if (newSelEnd < 0) {
7530 newSelEnd = Selection.getSelectionEnd(buf);
7531 }
7532 onSelectionChanged(newSelStart, newSelEnd);
7533 }
7534 }
Gilles Debunne8615ac92011-11-29 15:25:03 -08007535
Gilles Debunneb35ab7b2011-12-05 15:54:00 -08007536 if (what instanceof UpdateAppearance || what instanceof ParagraphStyle ||
7537 what instanceof CharacterStyle) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007538 if (ims == null || ims.mBatchEditNesting == 0) {
7539 invalidate();
Gilles Debunne83051b82012-02-24 20:01:13 -08007540 mHighlightPathBogus = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007541 checkForResize();
7542 } else {
7543 ims.mContentChanged = true;
7544 }
Gilles Debunneebc86af2012-04-20 15:10:47 -07007545 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007546 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
7547 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
Gilles Debunneebc86af2012-04-20 15:10:47 -07007548 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007549 }
7550
7551 if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
Gilles Debunne83051b82012-02-24 20:01:13 -08007552 mHighlightPathBogus = true;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07007553 if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
7554 ims.mSelectionModeChanged = true;
7555 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007556
7557 if (Selection.getSelectionStart(buf) >= 0) {
7558 if (ims == null || ims.mBatchEditNesting == 0) {
7559 invalidateCursor();
7560 } else {
7561 ims.mCursorChanged = true;
7562 }
7563 }
7564 }
Gilles Debunne6435a562011-08-04 21:22:30 -07007565
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007566 if (what instanceof ParcelableSpan) {
7567 // If this is a span that can be sent to a remote process,
7568 // the current extract editor would be interested in it.
Gilles Debunnec62589c2012-04-12 14:50:23 -07007569 if (ims != null && ims.mExtractedTextRequest != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007570 if (ims.mBatchEditNesting != 0) {
7571 if (oldStart >= 0) {
7572 if (ims.mChangedStart > oldStart) {
7573 ims.mChangedStart = oldStart;
7574 }
7575 if (ims.mChangedStart > oldEnd) {
7576 ims.mChangedStart = oldEnd;
7577 }
7578 }
7579 if (newStart >= 0) {
7580 if (ims.mChangedStart > newStart) {
7581 ims.mChangedStart = newStart;
7582 }
7583 if (ims.mChangedStart > newEnd) {
7584 ims.mChangedStart = newEnd;
7585 }
7586 }
7587 } else {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007588 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: "
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007589 + oldStart + "-" + oldEnd + ","
Gilles Debunnec62589c2012-04-12 14:50:23 -07007590 + newStart + "-" + newEnd + " " + what);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007591 ims.mContentChanged = true;
7592 }
7593 }
7594 }
Gilles Debunne6435a562011-08-04 21:22:30 -07007595
Gilles Debunne2d373a12012-04-20 15:32:19 -07007596 if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0 &&
7597 what instanceof SpellCheckSpan) {
Gilles Debunne69865bd2012-05-09 11:12:03 -07007598 mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
Gilles Debunne6435a562011-08-04 21:22:30 -07007599 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007600 }
7601
Gilles Debunne6435a562011-08-04 21:22:30 -07007602 /**
Romain Guydcc490f2010-02-24 17:59:35 -08007603 * @hide
7604 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007605 @Override
Romain Guya440b002010-02-24 15:57:54 -08007606 public void dispatchFinishTemporaryDetach() {
7607 mDispatchTemporaryDetach = true;
7608 super.dispatchFinishTemporaryDetach();
7609 mDispatchTemporaryDetach = false;
7610 }
7611
7612 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007613 public void onStartTemporaryDetach() {
Romain Guya440b002010-02-24 15:57:54 -08007614 super.onStartTemporaryDetach();
7615 // Only track when onStartTemporaryDetach() is called directly,
7616 // usually because this instance is an editable field in a list
7617 if (!mDispatchTemporaryDetach) mTemporaryDetach = true;
Gilles Debunne4b2274f2011-02-25 15:18:03 -08007618
Adam Powell057a5852012-05-11 10:28:38 -07007619 // Tell the editor that we are temporarily detached. It can use this to preserve
7620 // selection state as needed.
7621 if (mEditor != null) mEditor.mTemporaryDetach = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007622 }
Gilles Debunne3784a7f2011-07-15 13:49:38 -07007623
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007624 @Override
7625 public void onFinishTemporaryDetach() {
Romain Guya440b002010-02-24 15:57:54 -08007626 super.onFinishTemporaryDetach();
7627 // Only track when onStartTemporaryDetach() is called directly,
7628 // usually because this instance is an editable field in a list
7629 if (!mDispatchTemporaryDetach) mTemporaryDetach = false;
Adam Powell057a5852012-05-11 10:28:38 -07007630 if (mEditor != null) mEditor.mTemporaryDetach = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007631 }
Gilles Debunne3784a7f2011-07-15 13:49:38 -07007632
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007633 @Override
7634 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
7635 if (mTemporaryDetach) {
7636 // If we are temporarily in the detach state, then do nothing.
7637 super.onFocusChanged(focused, direction, previouslyFocusedRect);
7638 return;
7639 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007640
Gilles Debunne2d373a12012-04-20 15:32:19 -07007641 if (mEditor != null) mEditor.onFocusChanged(focused, direction);
Gilles Debunne03789e82010-09-07 19:07:17 -07007642
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007643 if (focused) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007644 if (mText instanceof Spannable) {
7645 Spannable sp = (Spannable) mText;
7646 MetaKeyKeyListener.resetMetaState(sp);
7647 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007648 }
7649
7650 startStopMarquee(focused);
7651
7652 if (mTransformation != null) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07007653 mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007654 }
7655
7656 super.onFocusChanged(focused, direction, previouslyFocusedRect);
7657 }
7658
7659 @Override
7660 public void onWindowFocusChanged(boolean hasWindowFocus) {
7661 super.onWindowFocusChanged(hasWindowFocus);
7662
Gilles Debunne2d373a12012-04-20 15:32:19 -07007663 if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007664
7665 startStopMarquee(hasWindowFocus);
7666 }
7667
Adam Powellba0a2c32010-09-28 17:41:23 -07007668 @Override
7669 protected void onVisibilityChanged(View changedView, int visibility) {
7670 super.onVisibilityChanged(changedView, visibility);
Gilles Debunne60e21862012-01-30 15:04:14 -08007671 if (mEditor != null && visibility != VISIBLE) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007672 mEditor.hideControllers();
Adam Powellba0a2c32010-09-28 17:41:23 -07007673 }
7674 }
7675
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007676 /**
7677 * Use {@link BaseInputConnection#removeComposingSpans
7678 * BaseInputConnection.removeComposingSpans()} to remove any IME composing
7679 * state from this text view.
7680 */
7681 public void clearComposingText() {
7682 if (mText instanceof Spannable) {
7683 BaseInputConnection.removeComposingSpans((Spannable)mText);
7684 }
7685 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07007686
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007687 @Override
7688 public void setSelected(boolean selected) {
7689 boolean wasSelected = isSelected();
7690
7691 super.setSelected(selected);
7692
7693 if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7694 if (selected) {
7695 startMarquee();
7696 } else {
7697 stopMarquee();
7698 }
7699 }
7700 }
7701
7702 @Override
7703 public boolean onTouchEvent(MotionEvent event) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07007704 final int action = event.getActionMasked();
Adam Powell965b9692010-10-21 18:44:32 -07007705
Gilles Debunne2d373a12012-04-20 15:32:19 -07007706 if (mEditor != null) mEditor.onTouchEvent(event);
Gilles Debunnebbb5d6e2010-06-21 16:21:51 -07007707
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007708 final boolean superResult = super.onTouchEvent(event);
7709
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007710 /*
7711 * Don't handle the release after a long press, because it will
7712 * move the selection away from whatever the menu action was
7713 * trying to affect.
7714 */
Gilles Debunne2d373a12012-04-20 15:32:19 -07007715 if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
7716 mEditor.mDiscardNextActionUp = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007717 return superResult;
7718 }
7719
Gilles Debunne70a63122011-09-01 13:27:33 -07007720 final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) &&
Gilles Debunne2d373a12012-04-20 15:32:19 -07007721 (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
Gilles Debunnec3e85a72011-01-21 08:46:06 -08007722
Gilles Debunne70a63122011-09-01 13:27:33 -07007723 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
Janos Levai042856c2010-10-15 02:53:58 +03007724 && mText instanceof Spannable && mLayout != null) {
Gilles Debunnef788a9f2010-07-22 10:17:23 -07007725 boolean handled = false;
7726
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07007727 if (mMovement != null) {
7728 handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
7729 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007730
Gilles Debunne60e21862012-01-30 15:04:14 -08007731 final boolean textIsSelectable = isTextSelectable();
7732 if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
Gilles Debunnef3895ed2010-12-21 12:53:58 -08007733 // The LinkMovementMethod which should handle taps on links has not been installed
Gilles Debunne70a63122011-09-01 13:27:33 -07007734 // on non editable text that support text selection.
7735 // We reproduce its behavior here to open links for these.
Gilles Debunnef3895ed2010-12-21 12:53:58 -08007736 ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
7737 getSelectionEnd(), ClickableSpan.class);
7738
Gilles Debunne822b8f02012-01-17 18:02:15 -08007739 if (links.length > 0) {
Gilles Debunnef3895ed2010-12-21 12:53:58 -08007740 links[0].onClick(this);
7741 handled = true;
7742 }
7743 }
7744
Gilles Debunne60e21862012-01-30 15:04:14 -08007745 if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
Gilles Debunne180bb1b2011-03-10 11:14:00 -08007746 // Show the IME, except when selecting in read-only text.
satok863fcd62011-06-21 17:38:02 +09007747 final InputMethodManager imm = InputMethodManager.peekInstance();
satoka67a3cf2011-09-07 17:14:03 +09007748 viewClicked(imm);
Gilles Debunne3473b2b2012-04-20 16:21:10 -07007749 if (!textIsSelectable && mEditor.mShowSoftInputOnFocus) {
Gilles Debunne180bb1b2011-03-10 11:14:00 -08007750 handled |= imm != null && imm.showSoftInput(this, 0);
Adam Powell879fb6b2010-09-20 11:23:56 -07007751 }
Gilles Debunnebbb5d6e2010-06-21 16:21:51 -07007752
Gilles Debunned88876a2012-03-16 17:34:04 -07007753 // The above condition ensures that the mEditor is not null
Gilles Debunne2d373a12012-04-20 15:32:19 -07007754 mEditor.onTouchUpEvent(event);
Gilles Debunne6435a562011-08-04 21:22:30 -07007755
7756 handled = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007757 }
7758
The Android Open Source Project4df24232009-03-05 14:34:35 -08007759 if (handled) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007760 return true;
7761 }
7762 }
7763
7764 return superResult;
7765 }
7766
Jeff Brown8f345672011-02-26 13:29:53 -08007767 @Override
7768 public boolean onGenericMotionEvent(MotionEvent event) {
7769 if (mMovement != null && mText instanceof Spannable && mLayout != null) {
7770 try {
7771 if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) {
7772 return true;
7773 }
7774 } catch (AbstractMethodError ex) {
7775 // onGenericMotionEvent was added to the MovementMethod interface in API 12.
7776 // Ignore its absence in case third party applications implemented the
7777 // interface directly.
7778 }
7779 }
7780 return super.onGenericMotionEvent(event);
7781 }
7782
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007783 /**
Gilles Debunne86b9c782010-11-11 10:43:48 -08007784 * @return True iff this TextView contains a text that can be edited, or if this is
7785 * a selectable TextView.
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007786 */
Gilles Debunned88876a2012-03-16 17:34:04 -07007787 boolean isTextEditable() {
Gilles Debunnef076eeb2010-11-29 11:32:53 -08007788 return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007789 }
7790
The Android Open Source Project4df24232009-03-05 14:34:35 -08007791 /**
7792 * Returns true, only while processing a touch gesture, if the initial
7793 * 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 -07007794 * its selection changed. Only valid while processing the touch gesture
Gilles Debunne053c4392012-03-15 15:35:26 -07007795 * of interest, in an editable text view.
The Android Open Source Project4df24232009-03-05 14:34:35 -08007796 */
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07007797 public boolean didTouchFocusSelect() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007798 return mEditor != null && mEditor.mTouchFocusSelected;
The Android Open Source Project4df24232009-03-05 14:34:35 -08007799 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07007800
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007801 @Override
7802 public void cancelLongPress() {
7803 super.cancelLongPress();
Gilles Debunne2d373a12012-04-20 15:32:19 -07007804 if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007805 }
Gilles Debunne70a63122011-09-01 13:27:33 -07007806
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007807 @Override
7808 public boolean onTrackballEvent(MotionEvent event) {
Gilles Debunne60e21862012-01-30 15:04:14 -08007809 if (mMovement != null && mText instanceof Spannable && mLayout != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007810 if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
7811 return true;
7812 }
7813 }
7814
7815 return super.onTrackballEvent(event);
7816 }
7817
7818 public void setScroller(Scroller s) {
7819 mScroller = s;
7820 }
7821
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007822 @Override
7823 protected float getLeftFadingEdgeStrength() {
Adam Powell282e3772011-08-30 16:51:11 -07007824 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
7825 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007826 if (mMarquee != null && !mMarquee.isStopped()) {
7827 final Marquee marquee = mMarquee;
Romain Guyc2303192009-04-03 17:37:18 -07007828 if (marquee.shouldDrawLeftFade()) {
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07007829 final float scroll = marquee.getScroll();
7830 return scroll / getHorizontalFadingEdgeLength();
Romain Guyc2303192009-04-03 17:37:18 -07007831 } else {
7832 return 0.0f;
7833 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007834 } else if (getLineCount() == 1) {
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07007835 final int layoutDirection = getLayoutDirection();
Fabrice Di Meglioc0053222011-06-13 12:16:51 -07007836 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07007837 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007838 case Gravity.LEFT:
7839 return 0.0f;
7840 case Gravity.RIGHT:
7841 return (mLayout.getLineRight(0) - (mRight - mLeft) -
7842 getCompoundPaddingLeft() - getCompoundPaddingRight() -
7843 mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
7844 case Gravity.CENTER_HORIZONTAL:
Raph Levien8079ae12013-09-26 15:57:07 -07007845 case Gravity.FILL_HORIZONTAL:
7846 final int textDirection = mLayout.getParagraphDirection(0);
7847 if (textDirection == Layout.DIR_LEFT_TO_RIGHT) {
7848 return 0.0f;
7849 } else {
7850 return (mLayout.getLineRight(0) - (mRight - mLeft) -
7851 getCompoundPaddingLeft() - getCompoundPaddingRight() -
7852 mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
7853 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007854 }
7855 }
7856 }
7857 return super.getLeftFadingEdgeStrength();
7858 }
7859
7860 @Override
7861 protected float getRightFadingEdgeStrength() {
Adam Powell282e3772011-08-30 16:51:11 -07007862 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
7863 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007864 if (mMarquee != null && !mMarquee.isStopped()) {
7865 final Marquee marquee = mMarquee;
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07007866 final float maxFadeScroll = marquee.getMaxFadeScroll();
7867 final float scroll = marquee.getScroll();
7868 return (maxFadeScroll - scroll) / getHorizontalFadingEdgeLength();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007869 } else if (getLineCount() == 1) {
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07007870 final int layoutDirection = getLayoutDirection();
Fabrice Di Meglioc0053222011-06-13 12:16:51 -07007871 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07007872 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007873 case Gravity.LEFT:
Romain Guy076dc9f2009-06-24 17:17:51 -07007874 final int textWidth = (mRight - mLeft) - getCompoundPaddingLeft() -
7875 getCompoundPaddingRight();
7876 final float lineWidth = mLayout.getLineWidth(0);
7877 return (lineWidth - textWidth) / getHorizontalFadingEdgeLength();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007878 case Gravity.RIGHT:
7879 return 0.0f;
7880 case Gravity.CENTER_HORIZONTAL:
Gilles Debunne44c14732010-10-19 11:56:59 -07007881 case Gravity.FILL_HORIZONTAL:
Raph Levien8079ae12013-09-26 15:57:07 -07007882 final int textDirection = mLayout.getParagraphDirection(0);
7883 if (textDirection == Layout.DIR_RIGHT_TO_LEFT) {
7884 return 0.0f;
7885 } else {
7886 return (mLayout.getLineWidth(0) - ((mRight - mLeft) -
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007887 getCompoundPaddingLeft() - getCompoundPaddingRight())) /
7888 getHorizontalFadingEdgeLength();
Raph Levien8079ae12013-09-26 15:57:07 -07007889 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007890 }
7891 }
7892 }
7893 return super.getRightFadingEdgeStrength();
7894 }
7895
7896 @Override
7897 protected int computeHorizontalScrollRange() {
Romain Guydac5f9f2010-07-08 11:40:54 -07007898 if (mLayout != null) {
7899 return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT ?
7900 (int) mLayout.getLineWidth(0) : mLayout.getWidth();
7901 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007902
7903 return super.computeHorizontalScrollRange();
7904 }
7905
7906 @Override
7907 protected int computeVerticalScrollRange() {
7908 if (mLayout != null)
7909 return mLayout.getHeight();
7910
7911 return super.computeVerticalScrollRange();
7912 }
7913
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07007914 @Override
7915 protected int computeVerticalScrollExtent() {
7916 return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
7917 }
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07007918
7919 @Override
Svetoslav Ganovea515ae2011-09-14 18:15:32 -07007920 public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
7921 super.findViewsWithText(outViews, searched, flags);
7922 if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
7923 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
7924 String searchedLowerCase = searched.toString().toLowerCase();
7925 String textLowerCase = mText.toString().toLowerCase();
7926 if (textLowerCase.contains(searchedLowerCase)) {
7927 outViews.add(this);
7928 }
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07007929 }
7930 }
7931
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007932 public enum BufferType {
7933 NORMAL, SPANNABLE, EDITABLE,
7934 }
7935
7936 /**
7937 * Returns the TextView_textColor attribute from the
John Spurlock330dd532012-12-18 12:03:11 -05007938 * TypedArray, if set, or the TextAppearance_textColor
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007939 * from the TextView_textAppearance attribute, if TextView_textColor
7940 * was not set directly.
7941 */
7942 public static ColorStateList getTextColors(Context context, TypedArray attrs) {
7943 ColorStateList colors;
7944 colors = attrs.getColorStateList(com.android.internal.R.styleable.
7945 TextView_textColor);
7946
7947 if (colors == null) {
7948 int ap = attrs.getResourceId(com.android.internal.R.styleable.
7949 TextView_textAppearance, -1);
7950 if (ap != -1) {
7951 TypedArray appearance;
7952 appearance = context.obtainStyledAttributes(ap,
7953 com.android.internal.R.styleable.TextAppearance);
7954 colors = appearance.getColorStateList(com.android.internal.R.styleable.
7955 TextAppearance_textColor);
7956 appearance.recycle();
7957 }
7958 }
7959
7960 return colors;
7961 }
7962
7963 /**
7964 * Returns the default color from the TextView_textColor attribute
7965 * from the AttributeSet, if set, or the default color from the
7966 * TextAppearance_textColor from the TextView_textAppearance attribute,
7967 * if TextView_textColor was not set directly.
7968 */
7969 public static int getTextColor(Context context,
7970 TypedArray attrs,
7971 int def) {
7972 ColorStateList colors = getTextColors(context, attrs);
7973
7974 if (colors == null) {
7975 return def;
7976 } else {
7977 return colors.getDefaultColor();
7978 }
7979 }
7980
7981 @Override
7982 public boolean onKeyShortcut(int keyCode, KeyEvent event) {
Jeff Brownc1df9072010-12-21 16:38:50 -08007983 final int filteredMetaState = event.getMetaState() & ~KeyEvent.META_CTRL_MASK;
7984 if (KeyEvent.metaStateHasNoModifiers(filteredMetaState)) {
7985 switch (keyCode) {
7986 case KeyEvent.KEYCODE_A:
7987 if (canSelectText()) {
7988 return onTextContextMenuItem(ID_SELECT_ALL);
7989 }
7990 break;
7991 case KeyEvent.KEYCODE_X:
7992 if (canCut()) {
7993 return onTextContextMenuItem(ID_CUT);
7994 }
7995 break;
7996 case KeyEvent.KEYCODE_C:
7997 if (canCopy()) {
7998 return onTextContextMenuItem(ID_COPY);
7999 }
8000 break;
8001 case KeyEvent.KEYCODE_V:
8002 if (canPaste()) {
8003 return onTextContextMenuItem(ID_PASTE);
8004 }
8005 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008006 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008007 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008008 return super.onKeyShortcut(keyCode, event);
8009 }
8010
Gilles Debunnecbcb3452010-12-17 15:31:02 -08008011 /**
8012 * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
8013 * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
Gilles Debunne2d373a12012-04-20 15:32:19 -07008014 * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
8015 * sufficient.
Gilles Debunnecbcb3452010-12-17 15:31:02 -08008016 */
Gilles Debunnebaaace52010-10-01 15:47:13 -07008017 private boolean canSelectText() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008018 return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008019 }
8020
Gilles Debunnecbcb3452010-12-17 15:31:02 -08008021 /**
8022 * Test based on the <i>intrinsic</i> charateristics of the TextView.
8023 * The text must be spannable and the movement method must allow for arbitary selection.
Gilles Debunne2d373a12012-04-20 15:32:19 -07008024 *
Gilles Debunnecbcb3452010-12-17 15:31:02 -08008025 * See also {@link #canSelectText()}.
8026 */
Gilles Debunned88876a2012-03-16 17:34:04 -07008027 boolean textCanBeSelected() {
Gilles Debunne05336272010-07-09 20:13:45 -07008028 // prepareCursorController() relies on this method.
8029 // If you change this condition, make sure prepareCursorController is called anywhere
8030 // the value of this condition might be changed.
Gilles Debunnebb588da2011-07-11 18:26:19 -07008031 if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07008032 return isTextEditable() ||
8033 (isTextSelectable() && mText instanceof Spannable && isEnabled());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008034 }
8035
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008036 private Locale getTextServicesLocale(boolean allowNullLocale) {
8037 // Start fetching the text services locale asynchronously.
8038 updateTextServicesLocaleAsync();
8039 // If !allowNullLocale and there is no cached text services locale, just return the default
8040 // locale.
8041 return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
8042 : mCurrentSpellCheckerLocaleCache;
8043 }
8044
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008045 /**
8046 * This is a temporary method. Future versions may support multi-locale text.
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09008047 * Caveat: This method may not return the latest text services locale, but this should be
8048 * acceptable and it's more important to make this method asynchronous.
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008049 *
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008050 * @return The locale that should be used for a word iterator
satok05f24702011-11-02 19:29:35 +09008051 * in this TextView, based on the current spell checker settings,
8052 * the current IME's locale, or the system default locale.
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008053 * Please note that a word iterator in this TextView is different from another word iterator
8054 * used by SpellChecker.java of TextView. This method should be used for the former.
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008055 * @hide
8056 */
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09008057 // TODO: Support multi-locale
8058 // TODO: Update the text services locale immediately after the keyboard locale is switched
8059 // by catching intent of keyboard switch event
satok05f24702011-11-02 19:29:35 +09008060 public Locale getTextServicesLocale() {
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008061 return getTextServicesLocale(false /* allowNullLocale */);
8062 }
8063
8064 /**
8065 * This is a temporary method. Future versions may support multi-locale text.
8066 * Caveat: This method may not return the latest spell checker locale, but this should be
8067 * acceptable and it's more important to make this method asynchronous.
8068 *
8069 * @return The locale that should be used for a spell checker in this TextView,
8070 * based on the current spell checker settings, the current IME's locale, or the system default
8071 * locale.
8072 * @hide
8073 */
8074 public Locale getSpellCheckerLocale() {
8075 return getTextServicesLocale(true /* allowNullLocale */);
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09008076 }
8077
8078 private void updateTextServicesLocaleAsync() {
Romain Guy31f05442013-06-03 14:19:54 -07008079 // AsyncTask.execute() uses a serial executor which means we don't have
8080 // to lock around updateTextServicesLocaleLocked() to prevent it from
8081 // being executed n times in parallel.
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09008082 AsyncTask.execute(new Runnable() {
8083 @Override
8084 public void run() {
Romain Guy31f05442013-06-03 14:19:54 -07008085 updateTextServicesLocaleLocked();
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09008086 }
8087 });
8088 }
8089
8090 private void updateTextServicesLocaleLocked() {
satok05f24702011-11-02 19:29:35 +09008091 final TextServicesManager textServicesManager = (TextServicesManager)
8092 mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
8093 final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008094 final Locale locale;
satok05f24702011-11-02 19:29:35 +09008095 if (subtype != null) {
satokf927e172012-05-24 16:52:54 +09008096 locale = SpellCheckerSubtype.constructLocaleFromString(subtype.getLocale());
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008097 } else {
8098 locale = null;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008099 }
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008100 mCurrentSpellCheckerLocaleCache = locale;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008101 }
8102
8103 void onLocaleChanged() {
8104 // Will be re-created on demand in getWordIterator with the proper new locale
Gilles Debunne2d373a12012-04-20 15:32:19 -07008105 mEditor.mWordIterator = null;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008106 }
8107
8108 /**
Gilles Debunned88876a2012-03-16 17:34:04 -07008109 * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
8110 * Made available to achieve a consistent behavior.
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008111 * @hide
8112 */
8113 public WordIterator getWordIterator() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008114 if (mEditor != null) {
8115 return mEditor.getWordIterator();
Gilles Debunned88876a2012-03-16 17:34:04 -07008116 } else {
8117 return null;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008118 }
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07008119 }
Gilles Debunnedf4ee432010-08-25 19:13:48 -07008120
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008121 @Override
Svetoslav Ganov736c2752011-04-22 18:30:36 -07008122 public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
Svetoslav Ganov887e1a12011-04-29 15:09:28 -07008123 super.onPopulateAccessibilityEvent(event);
8124
Svetoslav Ganov1d1e1102010-11-16 16:44:03 -08008125 final boolean isPassword = hasPasswordTransformationMethod();
alanv7d624192012-05-21 14:23:17 -07008126 if (!isPassword || shouldSpeakPasswordsForAccessibility()) {
8127 final CharSequence text = getTextForAccessibility();
Svetoslav Ganovd37848a2011-09-20 14:03:55 -07008128 if (!TextUtils.isEmpty(text)) {
svetoslavganov75986cf2009-05-14 22:28:01 -07008129 event.getText().add(text);
8130 }
svetoslavganov75986cf2009-05-14 22:28:01 -07008131 }
svetoslavganov75986cf2009-05-14 22:28:01 -07008132 }
8133
alanv7d624192012-05-21 14:23:17 -07008134 /**
8135 * @return true if the user has explicitly allowed accessibility services
8136 * to speak passwords.
8137 */
8138 private boolean shouldSpeakPasswordsForAccessibility() {
8139 return (Settings.Secure.getInt(mContext.getContentResolver(),
8140 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0) == 1);
8141 }
8142
Svetoslav Ganov30401322011-05-12 18:53:45 -07008143 @Override
8144 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
8145 super.onInitializeAccessibilityEvent(event);
8146
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08008147 event.setClassName(TextView.class.getName());
Svetoslav Ganov30401322011-05-12 18:53:45 -07008148 final boolean isPassword = hasPasswordTransformationMethod();
8149 event.setPassword(isPassword);
Svetoslav Ganova0156172011-06-26 17:55:44 -07008150
8151 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
8152 event.setFromIndex(Selection.getSelectionStart(mText));
8153 event.setToIndex(Selection.getSelectionEnd(mText));
8154 event.setItemCount(mText.length());
8155 }
Svetoslav Ganov30401322011-05-12 18:53:45 -07008156 }
8157
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07008158 @Override
8159 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
8160 super.onInitializeAccessibilityNodeInfo(info);
8161
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08008162 info.setClassName(TextView.class.getName());
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07008163 final boolean isPassword = hasPasswordTransformationMethod();
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08008164 info.setPassword(isPassword);
8165
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07008166 if (!isPassword) {
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07008167 info.setText(getTextForAccessibility());
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07008168 }
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008169
Svetoslavbcc46a02013-02-06 11:56:00 -08008170 if (mBufferType == BufferType.EDITABLE) {
8171 info.setEditable(true);
8172 }
8173
Svetoslav6254f482013-06-04 17:22:14 -07008174 if (mEditor != null) {
8175 info.setInputType(mEditor.mInputType);
Alan Viverette5b2081d2013-08-28 10:43:07 -07008176
8177 if (mEditor.mError != null) {
8178 info.setContentInvalid(true);
8179 }
Svetoslav6254f482013-06-04 17:22:14 -07008180 }
8181
Svetoslavdb7da0e2013-04-22 18:34:02 -07008182 if (!TextUtils.isEmpty(mText)) {
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008183 info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
8184 info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
8185 info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
8186 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
8187 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
8188 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
8189 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
8190 }
Svetoslavdb7da0e2013-04-22 18:34:02 -07008191
Svetoslav7c512842013-01-30 23:02:08 -08008192 if (isFocused()) {
8193 if (canSelectText()) {
8194 info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
8195 }
8196 if (canCopy()) {
8197 info.addAction(AccessibilityNodeInfo.ACTION_COPY);
8198 }
8199 if (canPaste()) {
8200 info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
8201 }
8202 if (canCut()) {
8203 info.addAction(AccessibilityNodeInfo.ACTION_CUT);
8204 }
8205 }
Alan Viverette058ac7c2013-08-19 16:44:30 -07008206
8207 if (!isSingleLine()) {
8208 info.setMultiLine(true);
8209 }
Svetoslav7c512842013-01-30 23:02:08 -08008210 }
8211
8212 @Override
8213 public boolean performAccessibilityAction(int action, Bundle arguments) {
8214 switch (action) {
8215 case AccessibilityNodeInfo.ACTION_COPY: {
8216 if (isFocused() && canCopy()) {
8217 if (onTextContextMenuItem(ID_COPY)) {
Svetoslav7c512842013-01-30 23:02:08 -08008218 return true;
8219 }
8220 }
8221 } return false;
8222 case AccessibilityNodeInfo.ACTION_PASTE: {
8223 if (isFocused() && canPaste()) {
8224 if (onTextContextMenuItem(ID_PASTE)) {
Svetoslav7c512842013-01-30 23:02:08 -08008225 return true;
8226 }
8227 }
8228 } return false;
8229 case AccessibilityNodeInfo.ACTION_CUT: {
8230 if (isFocused() && canCut()) {
8231 if (onTextContextMenuItem(ID_CUT)) {
Svetoslav7c512842013-01-30 23:02:08 -08008232 return true;
8233 }
8234 }
8235 } return false;
8236 case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
8237 if (isFocused() && canSelectText()) {
Svetoslav7c512842013-01-30 23:02:08 -08008238 CharSequence text = getIterableTextForAccessibility();
8239 if (text == null) {
8240 return false;
8241 }
Svetoslavd0c83cc2013-02-04 18:39:59 -08008242 final int start = (arguments != null) ? arguments.getInt(
8243 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
8244 final int end = (arguments != null) ? arguments.getInt(
8245 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
8246 if ((getSelectionStart() != start || getSelectionEnd() != end)) {
8247 // No arguments clears the selection.
8248 if (start == end && end == -1) {
8249 Selection.removeSelection((Spannable) text);
Svetoslavd0c83cc2013-02-04 18:39:59 -08008250 return true;
Svetoslav7c512842013-01-30 23:02:08 -08008251 }
Svetoslavd0c83cc2013-02-04 18:39:59 -08008252 if (start >= 0 && start <= end && end <= text.length()) {
8253 Selection.setSelection((Spannable) text, start, end);
8254 // Make sure selection mode is engaged.
8255 if (mEditor != null) {
8256 mEditor.startSelectionActionMode();
8257 }
Svetoslavd0c83cc2013-02-04 18:39:59 -08008258 return true;
8259 }
Svetoslav7c512842013-01-30 23:02:08 -08008260 }
8261 }
8262 } return false;
8263 default: {
8264 return super.performAccessibilityAction(action, arguments);
8265 }
8266 }
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07008267 }
8268
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07008269 @Override
8270 public void sendAccessibilityEvent(int eventType) {
8271 // Do not send scroll events since first they are not interesting for
8272 // accessibility and second such events a generated too frequently.
8273 // For details see the implementation of bringTextIntoView().
8274 if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
8275 return;
8276 }
8277 super.sendAccessibilityEvent(eventType);
8278 }
8279
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07008280 /**
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008281 * Gets the text reported for accessibility purposes.
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07008282 *
8283 * @return The accessibility text.
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008284 *
8285 * @hide
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07008286 */
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008287 public CharSequence getTextForAccessibility() {
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07008288 CharSequence text = getText();
8289 if (TextUtils.isEmpty(text)) {
8290 text = getHint();
8291 }
8292 return text;
8293 }
8294
svetoslavganov75986cf2009-05-14 22:28:01 -07008295 void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
8296 int fromIndex, int removedCount, int addedCount) {
8297 AccessibilityEvent event =
8298 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
8299 event.setFromIndex(fromIndex);
8300 event.setRemovedCount(removedCount);
8301 event.setAddedCount(addedCount);
8302 event.setBeforeText(beforeText);
8303 sendAccessibilityEventUnchecked(event);
8304 }
8305
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008306 /**
8307 * Returns whether this text view is a current input method target. The
8308 * default implementation just checks with {@link InputMethodManager}.
8309 */
8310 public boolean isInputMethodTarget() {
8311 InputMethodManager imm = InputMethodManager.peekInstance();
8312 return imm != null && imm.isActive(this);
8313 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07008314
Gilles Debunned88876a2012-03-16 17:34:04 -07008315 static final int ID_SELECT_ALL = android.R.id.selectAll;
8316 static final int ID_CUT = android.R.id.cut;
8317 static final int ID_COPY = android.R.id.copy;
8318 static final int ID_PASTE = android.R.id.paste;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008319
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008320 /**
8321 * Called when a context menu option for the text view is selected. Currently
Gilles Debunne07194e52011-11-02 14:18:44 -07008322 * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
8323 * {@link android.R.id#copy} or {@link android.R.id#paste}.
Gilles Debunnec59269f2011-04-22 11:46:09 -07008324 *
8325 * @return true if the context menu item action was performed.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008326 */
8327 public boolean onTextContextMenuItem(int id) {
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07008328 int min = 0;
8329 int max = mText.length();
Gilles Debunne64e54a62010-09-07 19:07:17 -07008330
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07008331 if (isFocused()) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07008332 final int selStart = getSelectionStart();
8333 final int selEnd = getSelectionEnd();
8334
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07008335 min = Math.max(0, Math.min(selStart, selEnd));
8336 max = Math.max(0, Math.max(selStart, selEnd));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008337 }
8338
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008339 switch (id) {
Jeff Brownc1df9072010-12-21 16:38:50 -08008340 case ID_SELECT_ALL:
Gilles Debunne299733e2011-02-07 17:11:41 -08008341 // This does not enter text selection mode. Text is highlighted, so that it can be
Gilles Debunnec59269f2011-04-22 11:46:09 -07008342 // bulk edited, like selectAllOnFocus does. Returns true even if text is empty.
Gilles Debunned88876a2012-03-16 17:34:04 -07008343 selectAllText();
Jeff Brownc1df9072010-12-21 16:38:50 -08008344 return true;
8345
8346 case ID_PASTE:
8347 paste(min, max);
8348 return true;
8349
8350 case ID_CUT:
Gilles Debunnecf68fee2011-09-29 10:55:36 -07008351 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
Gilles Debunne39ba6d92011-11-09 05:26:26 +01008352 deleteText_internal(min, max);
Jeff Brownc1df9072010-12-21 16:38:50 -08008353 stopSelectionActionMode();
8354 return true;
8355
8356 case ID_COPY:
Gilles Debunnecf68fee2011-09-29 10:55:36 -07008357 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
Jeff Brownc1df9072010-12-21 16:38:50 -08008358 stopSelectionActionMode();
8359 return true;
8360 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008361 return false;
8362 }
8363
Gilles Debunned88876a2012-03-16 17:34:04 -07008364 CharSequence getTransformedText(int start, int end) {
Gilles Debunnecf68fee2011-09-29 10:55:36 -07008365 return removeSuggestionSpans(mTransformed.subSequence(start, end));
8366 }
8367
Gilles Debunnee15b3582010-06-16 15:17:21 -07008368 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008369 public boolean performLongClick() {
Gilles Debunnee28454a2011-09-07 18:03:44 -07008370 boolean handled = false;
Gilles Debunnee28454a2011-09-07 18:03:44 -07008371
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008372 if (super.performLongClick()) {
Gilles Debunnee28454a2011-09-07 18:03:44 -07008373 handled = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008374 }
Gilles Debunnef170a342010-11-11 11:08:59 -08008375
Gilles Debunned88876a2012-03-16 17:34:04 -07008376 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008377 handled |= mEditor.performLongClick(handled);
Gilles Debunnee28454a2011-09-07 18:03:44 -07008378 }
8379
Gilles Debunne9f102ca2012-02-28 11:15:54 -08008380 if (handled) {
Gilles Debunnee28454a2011-09-07 18:03:44 -07008381 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
Gilles Debunne2d373a12012-04-20 15:32:19 -07008382 if (mEditor != null) mEditor.mDiscardNextActionUp = true;
Gilles Debunnef788a9f2010-07-22 10:17:23 -07008383 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008384
Gilles Debunne299733e2011-02-07 17:11:41 -08008385 return handled;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008386 }
8387
Gilles Debunne60e21862012-01-30 15:04:14 -08008388 @Override
8389 protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
8390 super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
Gilles Debunne6382ade2012-02-29 15:22:32 -08008391 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008392 mEditor.onScrollChanged();
Gilles Debunne60e21862012-01-30 15:04:14 -08008393 }
8394 }
8395
8396 /**
Gilles Debunne60e21862012-01-30 15:04:14 -08008397 * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
8398 * by the IME or by the spell checker as the user types. This is done by adding
8399 * {@link SuggestionSpan}s to the text.
8400 *
8401 * When suggestions are enabled (default), this list of suggestions will be displayed when the
8402 * user asks for them on these parts of the text. This value depends on the inputType of this
8403 * TextView.
8404 *
8405 * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
8406 *
8407 * In addition, the type variation must be one of
8408 * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
8409 * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
8410 * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
8411 * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
8412 * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
8413 *
8414 * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
8415 *
8416 * @return true if the suggestions popup window is enabled, based on the inputType.
8417 */
8418 public boolean isSuggestionsEnabled() {
8419 if (mEditor == null) return false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07008420 if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
8421 return false;
8422 }
8423 if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
Gilles Debunne60e21862012-01-30 15:04:14 -08008424
Gilles Debunne2d373a12012-04-20 15:32:19 -07008425 final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
Gilles Debunne60e21862012-01-30 15:04:14 -08008426 return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL ||
8427 variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT ||
8428 variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE ||
8429 variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE ||
8430 variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
8431 }
8432
8433 /**
8434 * If provided, this ActionMode.Callback will be used to create the ActionMode when text
8435 * selection is initiated in this View.
8436 *
8437 * The standard implementation populates the menu with a subset of Select All, Cut, Copy and
8438 * Paste actions, depending on what this View supports.
8439 *
8440 * A custom implementation can add new entries in the default menu in its
8441 * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, Menu)} method. The
8442 * default actions can also be removed from the menu using {@link Menu#removeItem(int)} and
8443 * passing {@link android.R.id#selectAll}, {@link android.R.id#cut}, {@link android.R.id#copy}
8444 * or {@link android.R.id#paste} ids as parameters.
8445 *
8446 * Returning false from
8447 * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, Menu)} will prevent
8448 * the action mode from being started.
8449 *
8450 * Action click events should be handled by the custom implementation of
8451 * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, MenuItem)}.
8452 *
8453 * Note that text selection mode is not started when a TextView receives focus and the
8454 * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
8455 * that case, to allow for quick replacement.
8456 */
8457 public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07008458 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07008459 mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
Gilles Debunne60e21862012-01-30 15:04:14 -08008460 }
8461
8462 /**
8463 * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
8464 *
8465 * @return The current custom selection callback.
8466 */
8467 public ActionMode.Callback getCustomSelectionActionModeCallback() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008468 return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
Gilles Debunne60e21862012-01-30 15:04:14 -08008469 }
8470
8471 /**
Gilles Debunne60e21862012-01-30 15:04:14 -08008472 * @hide
8473 */
8474 protected void stopSelectionActionMode() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008475 mEditor.stopSelectionActionMode();
Gilles Debunned88876a2012-03-16 17:34:04 -07008476 }
8477
8478 boolean canCut() {
8479 if (hasPasswordTransformationMethod()) {
8480 return false;
Gilles Debunne60e21862012-01-30 15:04:14 -08008481 }
Gilles Debunned88876a2012-03-16 17:34:04 -07008482
Gilles Debunne2d373a12012-04-20 15:32:19 -07008483 if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null &&
8484 mEditor.mKeyListener != null) {
Gilles Debunned88876a2012-03-16 17:34:04 -07008485 return true;
8486 }
8487
8488 return false;
8489 }
8490
8491 boolean canCopy() {
8492 if (hasPasswordTransformationMethod()) {
8493 return false;
8494 }
8495
Victoria Leased849f542014-01-15 15:15:03 -08008496 if (mText.length() > 0 && hasSelection() && mEditor != null) {
Gilles Debunned88876a2012-03-16 17:34:04 -07008497 return true;
8498 }
8499
8500 return false;
8501 }
8502
8503 boolean canPaste() {
8504 return (mText instanceof Editable &&
Gilles Debunne2d373a12012-04-20 15:32:19 -07008505 mEditor != null && mEditor.mKeyListener != null &&
Gilles Debunned88876a2012-03-16 17:34:04 -07008506 getSelectionStart() >= 0 &&
8507 getSelectionEnd() >= 0 &&
8508 ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
8509 hasPrimaryClip());
8510 }
8511
8512 boolean selectAllText() {
8513 final int length = mText.length();
8514 Selection.setSelection((Spannable) mText, 0, length);
8515 return length > 0;
8516 }
8517
8518 /**
8519 * Prepare text so that there are not zero or two spaces at beginning and end of region defined
8520 * by [min, max] when replacing this region by paste.
8521 * Note that if there were two spaces (or more) at that position before, they are kept. We just
8522 * make sure we do not add an extra one from the paste content.
8523 */
8524 long prepareSpacesAroundPaste(int min, int max, CharSequence paste) {
8525 if (paste.length() > 0) {
8526 if (min > 0) {
8527 final char charBefore = mTransformed.charAt(min - 1);
8528 final char charAfter = paste.charAt(0);
8529
8530 if (Character.isSpaceChar(charBefore) && Character.isSpaceChar(charAfter)) {
8531 // Two spaces at beginning of paste: remove one
8532 final int originalLength = mText.length();
8533 deleteText_internal(min - 1, min);
8534 // Due to filters, there is no guarantee that exactly one character was
8535 // removed: count instead.
8536 final int delta = mText.length() - originalLength;
8537 min += delta;
8538 max += delta;
8539 } else if (!Character.isSpaceChar(charBefore) && charBefore != '\n' &&
8540 !Character.isSpaceChar(charAfter) && charAfter != '\n') {
8541 // No space at beginning of paste: add one
8542 final int originalLength = mText.length();
8543 replaceText_internal(min, min, " ");
8544 // Taking possible filters into account as above.
8545 final int delta = mText.length() - originalLength;
8546 min += delta;
8547 max += delta;
8548 }
8549 }
8550
8551 if (max < mText.length()) {
8552 final char charBefore = paste.charAt(paste.length() - 1);
8553 final char charAfter = mTransformed.charAt(max);
8554
8555 if (Character.isSpaceChar(charBefore) && Character.isSpaceChar(charAfter)) {
8556 // Two spaces at end of paste: remove one
8557 deleteText_internal(max, max + 1);
8558 } else if (!Character.isSpaceChar(charBefore) && charBefore != '\n' &&
8559 !Character.isSpaceChar(charAfter) && charAfter != '\n') {
8560 // No space at end of paste: add one
8561 replaceText_internal(max, max, " ");
8562 }
8563 }
8564 }
8565
8566 return TextUtils.packRangeInLong(min, max);
Gilles Debunne60e21862012-01-30 15:04:14 -08008567 }
8568
8569 /**
8570 * Paste clipboard content between min and max positions.
8571 */
8572 private void paste(int min, int max) {
8573 ClipboardManager clipboard =
8574 (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
8575 ClipData clip = clipboard.getPrimaryClip();
8576 if (clip != null) {
8577 boolean didFirst = false;
8578 for (int i=0; i<clip.getItemCount(); i++) {
Dianne Hackbornacb69bb2012-04-13 15:36:06 -07008579 CharSequence paste = clip.getItemAt(i).coerceToStyledText(getContext());
Gilles Debunne60e21862012-01-30 15:04:14 -08008580 if (paste != null) {
8581 if (!didFirst) {
8582 long minMax = prepareSpacesAroundPaste(min, max, paste);
Gilles Debunne6c488de2012-03-01 16:20:35 -08008583 min = TextUtils.unpackRangeStartFromLong(minMax);
8584 max = TextUtils.unpackRangeEndFromLong(minMax);
Gilles Debunne60e21862012-01-30 15:04:14 -08008585 Selection.setSelection((Spannable) mText, max);
8586 ((Editable) mText).replace(min, max, paste);
8587 didFirst = true;
8588 } else {
8589 ((Editable) mText).insert(getSelectionEnd(), "\n");
8590 ((Editable) mText).insert(getSelectionEnd(), paste);
8591 }
8592 }
8593 }
8594 stopSelectionActionMode();
8595 LAST_CUT_OR_COPY_TIME = 0;
8596 }
8597 }
8598
8599 private void setPrimaryClip(ClipData clip) {
8600 ClipboardManager clipboard = (ClipboardManager) getContext().
8601 getSystemService(Context.CLIPBOARD_SERVICE);
8602 clipboard.setPrimaryClip(clip);
8603 LAST_CUT_OR_COPY_TIME = SystemClock.uptimeMillis();
8604 }
8605
Gilles Debunne60e21862012-01-30 15:04:14 -08008606 /**
8607 * Get the character offset closest to the specified absolute position. A typical use case is to
8608 * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
8609 *
8610 * @param x The horizontal absolute position of a point on screen
8611 * @param y The vertical absolute position of a point on screen
8612 * @return the character offset for the character whose position is closest to the specified
8613 * position. Returns -1 if there is no layout.
8614 */
8615 public int getOffsetForPosition(float x, float y) {
8616 if (getLayout() == null) return -1;
8617 final int line = getLineAtCoordinate(y);
8618 final int offset = getOffsetAtCoordinate(line, x);
8619 return offset;
8620 }
8621
Gilles Debunned88876a2012-03-16 17:34:04 -07008622 float convertToLocalHorizontalCoordinate(float x) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008623 x -= getTotalPaddingLeft();
8624 // Clamp the position to inside of the view.
8625 x = Math.max(0.0f, x);
8626 x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
8627 x += getScrollX();
8628 return x;
8629 }
8630
Gilles Debunned88876a2012-03-16 17:34:04 -07008631 int getLineAtCoordinate(float y) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008632 y -= getTotalPaddingTop();
8633 // Clamp the position to inside of the view.
8634 y = Math.max(0.0f, y);
8635 y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
8636 y += getScrollY();
8637 return getLayout().getLineForVertical((int) y);
8638 }
8639
8640 private int getOffsetAtCoordinate(int line, float x) {
8641 x = convertToLocalHorizontalCoordinate(x);
8642 return getLayout().getOffsetForHorizontal(line, x);
8643 }
8644
Gilles Debunne60e21862012-01-30 15:04:14 -08008645 @Override
8646 public boolean onDragEvent(DragEvent event) {
8647 switch (event.getAction()) {
8648 case DragEvent.ACTION_DRAG_STARTED:
Gilles Debunne2d373a12012-04-20 15:32:19 -07008649 return mEditor != null && mEditor.hasInsertionController();
Gilles Debunne60e21862012-01-30 15:04:14 -08008650
8651 case DragEvent.ACTION_DRAG_ENTERED:
8652 TextView.this.requestFocus();
8653 return true;
8654
8655 case DragEvent.ACTION_DRAG_LOCATION:
8656 final int offset = getOffsetForPosition(event.getX(), event.getY());
8657 Selection.setSelection((Spannable)mText, offset);
8658 return true;
8659
8660 case DragEvent.ACTION_DROP:
Gilles Debunne2d373a12012-04-20 15:32:19 -07008661 if (mEditor != null) mEditor.onDrop(event);
Gilles Debunne60e21862012-01-30 15:04:14 -08008662 return true;
8663
8664 case DragEvent.ACTION_DRAG_ENDED:
8665 case DragEvent.ACTION_DRAG_EXITED:
8666 default:
8667 return true;
8668 }
8669 }
8670
Gilles Debunne60e21862012-01-30 15:04:14 -08008671 boolean isInBatchEditMode() {
8672 if (mEditor == null) return false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07008673 final Editor.InputMethodState ims = mEditor.mInputMethodState;
Gilles Debunne60e21862012-01-30 15:04:14 -08008674 if (ims != null) {
8675 return ims.mBatchEditNesting > 0;
8676 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07008677 return mEditor.mInBatchEditControllers;
Gilles Debunne60e21862012-01-30 15:04:14 -08008678 }
8679
Fabrice Di Meglioa423f502013-05-14 13:20:32 -07008680 @Override
8681 public void onRtlPropertiesChanged(int layoutDirection) {
8682 super.onRtlPropertiesChanged(layoutDirection);
8683
8684 mTextDir = getTextDirectionHeuristic();
Fabrice Di Meglio22228fe2014-01-06 16:30:43 -08008685
8686 if (mLayout != null) {
8687 checkForRelayout();
8688 }
Fabrice Di Meglioa423f502013-05-14 13:20:32 -07008689 }
8690
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008691 TextDirectionHeuristic getTextDirectionHeuristic() {
Gilles Debunne60e21862012-01-30 15:04:14 -08008692 if (hasPasswordTransformationMethod()) {
Fabrice Di Meglio8701bb92012-11-14 19:57:11 -08008693 // passwords fields should be LTR
8694 return TextDirectionHeuristics.LTR;
Gilles Debunne60e21862012-01-30 15:04:14 -08008695 }
8696
8697 // Always need to resolve layout direction first
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07008698 final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
Gilles Debunne60e21862012-01-30 15:04:14 -08008699
8700 // Now, we can select the heuristic
Fabrice Di Meglio97e146c2012-09-23 15:45:16 -07008701 switch (getTextDirection()) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008702 default:
8703 case TEXT_DIRECTION_FIRST_STRONG:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008704 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
Gilles Debunne60e21862012-01-30 15:04:14 -08008705 TextDirectionHeuristics.FIRSTSTRONG_LTR);
Gilles Debunne60e21862012-01-30 15:04:14 -08008706 case TEXT_DIRECTION_ANY_RTL:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008707 return TextDirectionHeuristics.ANYRTL_LTR;
Gilles Debunne60e21862012-01-30 15:04:14 -08008708 case TEXT_DIRECTION_LTR:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008709 return TextDirectionHeuristics.LTR;
Gilles Debunne60e21862012-01-30 15:04:14 -08008710 case TEXT_DIRECTION_RTL:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008711 return TextDirectionHeuristics.RTL;
Gilles Debunne60e21862012-01-30 15:04:14 -08008712 case TEXT_DIRECTION_LOCALE:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008713 return TextDirectionHeuristics.LOCALE;
Gilles Debunne60e21862012-01-30 15:04:14 -08008714 }
8715 }
8716
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008717 /**
8718 * @hide
8719 */
Fabrice Di Megliob03b4342012-06-04 12:55:30 -07008720 @Override
8721 public void onResolveDrawables(int layoutDirection) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008722 // No need to resolve twice
Fabrice Di Meglio1957d282012-10-25 17:42:39 -07008723 if (mLastLayoutDirection == layoutDirection) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008724 return;
8725 }
Fabrice Di Meglio1957d282012-10-25 17:42:39 -07008726 mLastLayoutDirection = layoutDirection;
Gilles Debunne60e21862012-01-30 15:04:14 -08008727
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -08008728 // Resolve drawables
8729 if (mDrawables != null) {
8730 mDrawables.resolveWithLayoutDirection(layoutDirection);
Fabrice Di Megliob03b4342012-06-04 12:55:30 -07008731 }
8732 }
8733
Fabrice Di Meglio84ebb352012-10-11 16:27:37 -07008734 /**
8735 * @hide
8736 */
Gilles Debunne60e21862012-01-30 15:04:14 -08008737 protected void resetResolvedDrawables() {
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -08008738 super.resetResolvedDrawables();
Fabrice Di Meglio1957d282012-10-25 17:42:39 -07008739 mLastLayoutDirection = -1;
Gilles Debunne60e21862012-01-30 15:04:14 -08008740 }
8741
8742 /**
8743 * @hide
8744 */
8745 protected void viewClicked(InputMethodManager imm) {
8746 if (imm != null) {
8747 imm.viewClicked(this);
8748 }
8749 }
8750
8751 /**
8752 * Deletes the range of text [start, end[.
8753 * @hide
8754 */
8755 protected void deleteText_internal(int start, int end) {
8756 ((Editable) mText).delete(start, end);
8757 }
8758
8759 /**
8760 * Replaces the range of text [start, end[ by replacement text
8761 * @hide
8762 */
8763 protected void replaceText_internal(int start, int end, CharSequence text) {
8764 ((Editable) mText).replace(start, end, text);
8765 }
8766
8767 /**
8768 * Sets a span on the specified range of text
8769 * @hide
8770 */
8771 protected void setSpan_internal(Object span, int start, int end, int flags) {
8772 ((Editable) mText).setSpan(span, start, end, flags);
8773 }
8774
8775 /**
8776 * Moves the cursor to the specified offset position in text
8777 * @hide
8778 */
8779 protected void setCursorPosition_internal(int start, int end) {
8780 Selection.setSelection(((Editable) mText), start, end);
8781 }
8782
8783 /**
8784 * An Editor should be created as soon as any of the editable-specific fields (grouped
8785 * inside the Editor object) is assigned to a non-default value.
8786 * This method will create the Editor if needed.
8787 *
8788 * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
8789 * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
8790 * Editor for backward compatibility, as soon as one of these fields is assigned.
8791 *
8792 * Also note that for performance reasons, the mEditor is created when needed, but not
8793 * reset when no more edit-specific fields are needed.
8794 */
Gilles Debunne5fae9962012-05-08 14:53:20 -07008795 private void createEditorIfNeeded() {
Gilles Debunne60e21862012-01-30 15:04:14 -08008796 if (mEditor == null) {
Gilles Debunned88876a2012-03-16 17:34:04 -07008797 mEditor = new Editor(this);
Gilles Debunne60e21862012-01-30 15:04:14 -08008798 }
8799 }
8800
Gilles Debunne60e21862012-01-30 15:04:14 -08008801 /**
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008802 * @hide
8803 */
8804 @Override
8805 public CharSequence getIterableTextForAccessibility() {
Svetoslavdb7da0e2013-04-22 18:34:02 -07008806 if (!(mText instanceof Spannable)) {
8807 setText(mText, BufferType.SPANNABLE);
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008808 }
Svetoslavdb7da0e2013-04-22 18:34:02 -07008809 return mText;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008810 }
8811
8812 /**
8813 * @hide
8814 */
8815 @Override
8816 public TextSegmentIterator getIteratorForGranularity(int granularity) {
8817 switch (granularity) {
8818 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
8819 Spannable text = (Spannable) getIterableTextForAccessibility();
8820 if (!TextUtils.isEmpty(text) && getLayout() != null) {
8821 AccessibilityIterators.LineTextSegmentIterator iterator =
8822 AccessibilityIterators.LineTextSegmentIterator.getInstance();
8823 iterator.initialize(text, getLayout());
8824 return iterator;
8825 }
8826 } break;
8827 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
8828 Spannable text = (Spannable) getIterableTextForAccessibility();
8829 if (!TextUtils.isEmpty(text) && getLayout() != null) {
8830 AccessibilityIterators.PageTextSegmentIterator iterator =
8831 AccessibilityIterators.PageTextSegmentIterator.getInstance();
8832 iterator.initialize(this);
8833 return iterator;
8834 }
8835 } break;
8836 }
8837 return super.getIteratorForGranularity(granularity);
8838 }
8839
8840 /**
8841 * @hide
8842 */
8843 @Override
Svetoslav7c512842013-01-30 23:02:08 -08008844 public int getAccessibilitySelectionStart() {
Svetoslavdb7da0e2013-04-22 18:34:02 -07008845 return getSelectionStart();
Svetoslav7c512842013-01-30 23:02:08 -08008846 }
8847
8848 /**
8849 * @hide
8850 */
8851 public boolean isAccessibilitySelectionExtendable() {
8852 return true;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008853 }
8854
8855 /**
8856 * @hide
8857 */
8858 @Override
Svetoslav7c512842013-01-30 23:02:08 -08008859 public int getAccessibilitySelectionEnd() {
Svetoslavdb7da0e2013-04-22 18:34:02 -07008860 return getSelectionEnd();
Svetoslav7c512842013-01-30 23:02:08 -08008861 }
8862
8863 /**
8864 * @hide
8865 */
8866 @Override
8867 public void setAccessibilitySelection(int start, int end) {
8868 if (getAccessibilitySelectionStart() == start
8869 && getAccessibilitySelectionEnd() == end) {
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008870 return;
8871 }
Svetoslavabad55d2013-05-07 18:49:51 -07008872 // Hide all selection controllers used for adjusting selection
8873 // since we are doing so explicitlty by other means and these
8874 // controllers interact with how selection behaves.
8875 if (mEditor != null) {
8876 mEditor.hideControllers();
8877 }
Svetoslav7c512842013-01-30 23:02:08 -08008878 CharSequence text = getIterableTextForAccessibility();
Svetoslavabad55d2013-05-07 18:49:51 -07008879 if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
Svetoslav7c512842013-01-30 23:02:08 -08008880 Selection.setSelection((Spannable) text, start, end);
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008881 } else {
Svetoslav7c512842013-01-30 23:02:08 -08008882 Selection.removeSelection((Spannable) text);
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008883 }
8884 }
8885
8886 /**
Gilles Debunne60e21862012-01-30 15:04:14 -08008887 * User interface state that is stored by TextView for implementing
8888 * {@link View#onSaveInstanceState}.
8889 */
8890 public static class SavedState extends BaseSavedState {
8891 int selStart;
8892 int selEnd;
8893 CharSequence text;
8894 boolean frozenWithFocus;
8895 CharSequence error;
8896
8897 SavedState(Parcelable superState) {
8898 super(superState);
8899 }
8900
8901 @Override
8902 public void writeToParcel(Parcel out, int flags) {
8903 super.writeToParcel(out, flags);
8904 out.writeInt(selStart);
8905 out.writeInt(selEnd);
8906 out.writeInt(frozenWithFocus ? 1 : 0);
8907 TextUtils.writeToParcel(text, out, flags);
8908
8909 if (error == null) {
8910 out.writeInt(0);
8911 } else {
8912 out.writeInt(1);
8913 TextUtils.writeToParcel(error, out, flags);
8914 }
8915 }
8916
8917 @Override
8918 public String toString() {
8919 String str = "TextView.SavedState{"
8920 + Integer.toHexString(System.identityHashCode(this))
8921 + " start=" + selStart + " end=" + selEnd;
8922 if (text != null) {
8923 str += " text=" + text;
8924 }
8925 return str + "}";
8926 }
8927
8928 @SuppressWarnings("hiding")
8929 public static final Parcelable.Creator<SavedState> CREATOR
8930 = new Parcelable.Creator<SavedState>() {
8931 public SavedState createFromParcel(Parcel in) {
8932 return new SavedState(in);
8933 }
8934
8935 public SavedState[] newArray(int size) {
8936 return new SavedState[size];
8937 }
8938 };
8939
8940 private SavedState(Parcel in) {
8941 super(in);
8942 selStart = in.readInt();
8943 selEnd = in.readInt();
8944 frozenWithFocus = (in.readInt() != 0);
8945 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
8946
8947 if (in.readInt() != 0) {
8948 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
8949 }
8950 }
8951 }
8952
8953 private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
8954 private char[] mChars;
8955 private int mStart, mLength;
8956
8957 public CharWrapper(char[] chars, int start, int len) {
8958 mChars = chars;
8959 mStart = start;
8960 mLength = len;
8961 }
8962
8963 /* package */ void set(char[] chars, int start, int len) {
8964 mChars = chars;
8965 mStart = start;
8966 mLength = len;
8967 }
8968
8969 public int length() {
8970 return mLength;
8971 }
8972
8973 public char charAt(int off) {
8974 return mChars[off + mStart];
8975 }
8976
8977 @Override
8978 public String toString() {
8979 return new String(mChars, mStart, mLength);
8980 }
8981
8982 public CharSequence subSequence(int start, int end) {
8983 if (start < 0 || end < 0 || start > mLength || end > mLength) {
8984 throw new IndexOutOfBoundsException(start + ", " + end);
8985 }
8986
8987 return new String(mChars, start + mStart, end - start);
8988 }
8989
8990 public void getChars(int start, int end, char[] buf, int off) {
8991 if (start < 0 || end < 0 || start > mLength || end > mLength) {
8992 throw new IndexOutOfBoundsException(start + ", " + end);
8993 }
8994
8995 System.arraycopy(mChars, start + mStart, buf, off, end - start);
8996 }
8997
8998 public void drawText(Canvas c, int start, int end,
8999 float x, float y, Paint p) {
9000 c.drawText(mChars, start + mStart, end - start, x, y, p);
9001 }
9002
9003 public void drawTextRun(Canvas c, int start, int end,
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07009004 int contextStart, int contextEnd, float x, float y, int flags, Paint p) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009005 int count = end - start;
9006 int contextCount = contextEnd - contextStart;
9007 c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07009008 contextCount, x, y, flags, p);
Gilles Debunne60e21862012-01-30 15:04:14 -08009009 }
9010
9011 public float measureText(int start, int end, Paint p) {
9012 return p.measureText(mChars, start + mStart, end - start);
9013 }
9014
9015 public int getTextWidths(int start, int end, float[] widths, Paint p) {
9016 return p.getTextWidths(mChars, start + mStart, end - start, widths);
9017 }
9018
9019 public float getTextRunAdvances(int start, int end, int contextStart,
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07009020 int contextEnd, int flags, float[] advances, int advancesIndex,
Gilles Debunne60e21862012-01-30 15:04:14 -08009021 Paint p) {
9022 int count = end - start;
9023 int contextCount = contextEnd - contextStart;
9024 return p.getTextRunAdvances(mChars, start + mStart, count,
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07009025 contextStart + mStart, contextCount, flags, advances,
Gilles Debunne60e21862012-01-30 15:04:14 -08009026 advancesIndex);
9027 }
9028
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07009029 public int getTextRunCursor(int contextStart, int contextEnd, int flags,
Gilles Debunne60e21862012-01-30 15:04:14 -08009030 int offset, int cursorOpt, Paint p) {
9031 int contextCount = contextEnd - contextStart;
9032 return p.getTextRunCursor(mChars, contextStart + mStart,
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07009033 contextCount, flags, offset + mStart, cursorOpt);
Gilles Debunne60e21862012-01-30 15:04:14 -08009034 }
9035 }
9036
Gilles Debunne60e21862012-01-30 15:04:14 -08009037 private static final class Marquee extends Handler {
9038 // TODO: Add an option to configure this
9039 private static final float MARQUEE_DELTA_MAX = 0.07f;
9040 private static final int MARQUEE_DELAY = 1200;
9041 private static final int MARQUEE_RESTART_DELAY = 1200;
9042 private static final int MARQUEE_RESOLUTION = 1000 / 30;
9043 private static final int MARQUEE_PIXELS_PER_SECOND = 30;
9044
9045 private static final byte MARQUEE_STOPPED = 0x0;
9046 private static final byte MARQUEE_STARTING = 0x1;
9047 private static final byte MARQUEE_RUNNING = 0x2;
9048
9049 private static final int MESSAGE_START = 0x1;
9050 private static final int MESSAGE_TICK = 0x2;
9051 private static final int MESSAGE_RESTART = 0x3;
9052
9053 private final WeakReference<TextView> mView;
9054
9055 private byte mStatus = MARQUEE_STOPPED;
9056 private final float mScrollUnit;
9057 private float mMaxScroll;
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07009058 private float mMaxFadeScroll;
Gilles Debunne60e21862012-01-30 15:04:14 -08009059 private float mGhostStart;
9060 private float mGhostOffset;
9061 private float mFadeStop;
9062 private int mRepeatLimit;
9063
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07009064 private float mScroll;
Gilles Debunne60e21862012-01-30 15:04:14 -08009065
9066 Marquee(TextView v) {
9067 final float density = v.getContext().getResources().getDisplayMetrics().density;
9068 mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / MARQUEE_RESOLUTION;
9069 mView = new WeakReference<TextView>(v);
9070 }
9071
9072 @Override
9073 public void handleMessage(Message msg) {
9074 switch (msg.what) {
9075 case MESSAGE_START:
9076 mStatus = MARQUEE_RUNNING;
9077 tick();
9078 break;
9079 case MESSAGE_TICK:
9080 tick();
9081 break;
9082 case MESSAGE_RESTART:
9083 if (mStatus == MARQUEE_RUNNING) {
9084 if (mRepeatLimit >= 0) {
9085 mRepeatLimit--;
9086 }
9087 start(mRepeatLimit);
9088 }
9089 break;
9090 }
9091 }
9092
9093 void tick() {
9094 if (mStatus != MARQUEE_RUNNING) {
9095 return;
9096 }
9097
9098 removeMessages(MESSAGE_TICK);
9099
9100 final TextView textView = mView.get();
9101 if (textView != null && (textView.isFocused() || textView.isSelected())) {
9102 mScroll += mScrollUnit;
9103 if (mScroll > mMaxScroll) {
9104 mScroll = mMaxScroll;
9105 sendEmptyMessageDelayed(MESSAGE_RESTART, MARQUEE_RESTART_DELAY);
9106 } else {
9107 sendEmptyMessageDelayed(MESSAGE_TICK, MARQUEE_RESOLUTION);
9108 }
9109 textView.invalidate();
9110 }
9111 }
9112
9113 void stop() {
9114 mStatus = MARQUEE_STOPPED;
9115 removeMessages(MESSAGE_START);
9116 removeMessages(MESSAGE_RESTART);
9117 removeMessages(MESSAGE_TICK);
9118 resetScroll();
9119 }
9120
9121 private void resetScroll() {
9122 mScroll = 0.0f;
9123 final TextView textView = mView.get();
9124 if (textView != null) textView.invalidate();
9125 }
9126
9127 void start(int repeatLimit) {
9128 if (repeatLimit == 0) {
9129 stop();
9130 return;
9131 }
9132 mRepeatLimit = repeatLimit;
9133 final TextView textView = mView.get();
9134 if (textView != null && textView.mLayout != null) {
9135 mStatus = MARQUEE_STARTING;
9136 mScroll = 0.0f;
9137 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
9138 textView.getCompoundPaddingRight();
9139 final float lineWidth = textView.mLayout.getLineWidth(0);
9140 final float gap = textWidth / 3.0f;
9141 mGhostStart = lineWidth - textWidth + gap;
9142 mMaxScroll = mGhostStart + textWidth;
9143 mGhostOffset = lineWidth + gap;
9144 mFadeStop = lineWidth + textWidth / 6.0f;
9145 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
9146
9147 textView.invalidate();
9148 sendEmptyMessageDelayed(MESSAGE_START, MARQUEE_DELAY);
9149 }
9150 }
9151
9152 float getGhostOffset() {
9153 return mGhostOffset;
9154 }
9155
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07009156 float getScroll() {
9157 return mScroll;
9158 }
9159
9160 float getMaxFadeScroll() {
9161 return mMaxFadeScroll;
9162 }
9163
Gilles Debunne60e21862012-01-30 15:04:14 -08009164 boolean shouldDrawLeftFade() {
9165 return mScroll <= mFadeStop;
9166 }
9167
9168 boolean shouldDrawGhost() {
9169 return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
9170 }
9171
9172 boolean isRunning() {
9173 return mStatus == MARQUEE_RUNNING;
9174 }
9175
9176 boolean isStopped() {
9177 return mStatus == MARQUEE_STOPPED;
9178 }
9179 }
9180
Gilles Debunne60e21862012-01-30 15:04:14 -08009181 private class ChangeWatcher implements TextWatcher, SpanWatcher {
9182
9183 private CharSequence mBeforeText;
9184
Gilles Debunne60e21862012-01-30 15:04:14 -08009185 public void beforeTextChanged(CharSequence buffer, int start,
9186 int before, int after) {
9187 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start
9188 + " before=" + before + " after=" + after + ": " + buffer);
9189
9190 if (AccessibilityManager.getInstance(mContext).isEnabled()
Svetoslav Ganov72bba582012-11-05 13:53:43 -08009191 && ((!isPasswordInputType(getInputType()) && !hasPasswordTransformationMethod())
9192 || shouldSpeakPasswordsForAccessibility())) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009193 mBeforeText = buffer.toString();
9194 }
9195
9196 TextView.this.sendBeforeTextChanged(buffer, start, before, after);
9197 }
9198
Gilles Debunned88876a2012-03-16 17:34:04 -07009199 public void onTextChanged(CharSequence buffer, int start, int before, int after) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009200 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start
9201 + " before=" + before + " after=" + after + ": " + buffer);
9202 TextView.this.handleTextChanged(buffer, start, before, after);
9203
Gilles Debunne60e21862012-01-30 15:04:14 -08009204 if (AccessibilityManager.getInstance(mContext).isEnabled() &&
9205 (isFocused() || isSelected() && isShown())) {
9206 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
9207 mBeforeText = null;
9208 }
9209 }
9210
9211 public void afterTextChanged(Editable buffer) {
9212 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer);
9213 TextView.this.sendAfterTextChanged(buffer);
9214
9215 if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
9216 MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
9217 }
9218 }
9219
Gilles Debunned88876a2012-03-16 17:34:04 -07009220 public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009221 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
9222 + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
9223 TextView.this.spanChange(buf, what, s, st, e, en);
9224 }
9225
9226 public void onSpanAdded(Spannable buf, Object what, int s, int e) {
9227 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e
9228 + " what=" + what + ": " + buf);
9229 TextView.this.spanChange(buf, what, -1, s, -1, e);
9230 }
9231
9232 public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
9233 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e
9234 + " what=" + what + ": " + buf);
9235 TextView.this.spanChange(buf, what, s, -1, e, -1);
9236 }
satoka67a3cf2011-09-07 17:14:03 +09009237 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009238}