blob: 22bfadb3811ccbdd1b974f8812e6a1e7c010b4f5 [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;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.content.res.ColorStateList;
Christopher Tate1373a8e2011-11-10 19:59:13 -080024import android.content.res.CompatibilityInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080025import android.content.res.Resources;
26import android.content.res.TypedArray;
27import android.content.res.XmlResourceParser;
28import android.graphics.Canvas;
29import android.graphics.Paint;
30import android.graphics.Path;
31import android.graphics.Rect;
32import android.graphics.RectF;
33import android.graphics.Typeface;
34import android.graphics.drawable.Drawable;
Gilles Debunne64e54a62010-09-07 19:07:17 -070035import android.inputmethodservice.ExtractEditText;
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +090036import android.os.AsyncTask;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080037import android.os.Bundle;
38import android.os.Handler;
svetoslavganov75986cf2009-05-14 22:28:01 -070039import android.os.Message;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040import android.os.Parcel;
41import android.os.Parcelable;
42import android.os.SystemClock;
alanv7d624192012-05-21 14:23:17 -070043import android.provider.Settings;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080044import android.text.BoringLayout;
45import android.text.DynamicLayout;
46import android.text.Editable;
47import android.text.GetChars;
48import android.text.GraphicsOperations;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080049import android.text.InputFilter;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -070050import android.text.InputType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080051import android.text.Layout;
52import android.text.ParcelableSpan;
53import android.text.Selection;
54import android.text.SpanWatcher;
55import android.text.Spannable;
svetoslavganov75986cf2009-05-14 22:28:01 -070056import android.text.SpannableString;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080057import android.text.Spanned;
58import android.text.SpannedString;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080059import android.text.StaticLayout;
Doug Feltcb3791202011-07-07 11:57:48 -070060import android.text.TextDirectionHeuristic;
61import android.text.TextDirectionHeuristics;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080062import android.text.TextPaint;
63import android.text.TextUtils;
Adam Powell282e3772011-08-30 16:51:11 -070064import android.text.TextUtils.TruncateAt;
Gilles Debunne0eea6682011-08-29 13:30:31 -070065import android.text.TextWatcher;
Adam Powell7f8f79a2011-07-07 18:35:54 -070066import android.text.method.AllCapsTransformationMethod;
Gilles Debunne86b9c782010-11-11 10:43:48 -080067import android.text.method.ArrowKeyMovementMethod;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080068import android.text.method.DateKeyListener;
69import android.text.method.DateTimeKeyListener;
70import android.text.method.DialerKeyListener;
71import android.text.method.DigitsKeyListener;
72import android.text.method.KeyListener;
73import android.text.method.LinkMovementMethod;
74import android.text.method.MetaKeyKeyListener;
75import android.text.method.MovementMethod;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080076import android.text.method.PasswordTransformationMethod;
77import android.text.method.SingleLineTransformationMethod;
78import android.text.method.TextKeyListener;
svetoslavganov75986cf2009-05-14 22:28:01 -070079import android.text.method.TimeKeyListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080080import android.text.method.TransformationMethod;
Adam Powell7f8f79a2011-07-07 18:35:54 -070081import android.text.method.TransformationMethod2;
Gilles Debunne214a8622011-04-26 15:44:37 -070082import android.text.method.WordIterator;
Gilles Debunneb35ab7b2011-12-05 15:54:00 -080083import android.text.style.CharacterStyle;
Gilles Debunnef3895ed2010-12-21 12:53:58 -080084import android.text.style.ClickableSpan;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080085import android.text.style.ParagraphStyle;
Gilles Debunne6435a562011-08-04 21:22:30 -070086import android.text.style.SpellCheckSpan;
Gilles Debunne2037b822011-04-22 13:07:33 -070087import android.text.style.SuggestionSpan;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080088import android.text.style.URLSpan;
89import android.text.style.UpdateAppearance;
90import android.text.util.Linkify;
91import android.util.AttributeSet;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080092import android.util.FloatMath;
svetoslavganov75986cf2009-05-14 22:28:01 -070093import android.util.Log;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080094import android.util.TypedValue;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -070095import android.view.AccessibilityIterators.TextSegmentIterator;
Gilles Debunne27113f82010-08-23 12:09:14 -070096import android.view.ActionMode;
Gilles Debunnef170a342010-11-11 11:08:59 -080097import android.view.DragEvent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080098import android.view.Gravity;
Gilles Debunnef788a9f2010-07-22 10:17:23 -070099import android.view.HapticFeedbackConstants;
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800100import android.view.KeyCharacterMap;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800101import android.view.KeyEvent;
Gilles Debunnef788a9f2010-07-22 10:17:23 -0700102import android.view.Menu;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800103import android.view.MenuItem;
104import android.view.MotionEvent;
105import android.view.View;
Gilles Debunne65f60412010-10-15 16:18:35 -0700106import android.view.ViewConfiguration;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800107import android.view.ViewDebug;
Gilles Debunne27113f82010-08-23 12:09:14 -0700108import android.view.ViewGroup.LayoutParams;
Gilles Debunne3784a7f2011-07-15 13:49:38 -0700109import android.view.ViewRootImpl;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800110import android.view.ViewTreeObserver;
svetoslavganov75986cf2009-05-14 22:28:01 -0700111import android.view.accessibility.AccessibilityEvent;
112import android.view.accessibility.AccessibilityManager;
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700113import android.view.accessibility.AccessibilityNodeInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800114import android.view.animation.AnimationUtils;
115import android.view.inputmethod.BaseInputConnection;
116import android.view.inputmethod.CompletionInfo;
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -0800117import android.view.inputmethod.CorrectionInfo;
svetoslavganov75986cf2009-05-14 22:28:01 -0700118import android.view.inputmethod.EditorInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800119import android.view.inputmethod.ExtractedText;
120import android.view.inputmethod.ExtractedTextRequest;
121import android.view.inputmethod.InputConnection;
122import android.view.inputmethod.InputMethodManager;
satok05f24702011-11-02 19:29:35 +0900123import android.view.textservice.SpellCheckerSubtype;
124import android.view.textservice.TextServicesManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800125import android.widget.RemoteViews.RemoteView;
126
Gilles Debunne22378292011-08-12 10:38:52 -0700127import com.android.internal.util.FastMath;
128import com.android.internal.widget.EditableInputConnection;
129
130import org.xmlpull.v1.XmlPullParserException;
131
Gilles Debunne27113f82010-08-23 12:09:14 -0700132import java.io.IOException;
133import java.lang.ref.WeakReference;
134import java.util.ArrayList;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -0700135import java.util.Locale;
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +0900136import java.util.concurrent.locks.ReentrantLock;
Gilles Debunne27113f82010-08-23 12:09:14 -0700137
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800138/**
139 * Displays text to the user and optionally allows them to edit it. A TextView
140 * is a complete text editor, however the basic class is configured to not
141 * allow editing; see {@link EditText} for a subclass that configures the text
142 * view for editing.
143 *
144 * <p>
145 * <b>XML attributes</b>
146 * <p>
147 * See {@link android.R.styleable#TextView TextView Attributes},
148 * {@link android.R.styleable#View View Attributes}
149 *
150 * @attr ref android.R.styleable#TextView_text
151 * @attr ref android.R.styleable#TextView_bufferType
152 * @attr ref android.R.styleable#TextView_hint
153 * @attr ref android.R.styleable#TextView_textColor
154 * @attr ref android.R.styleable#TextView_textColorHighlight
155 * @attr ref android.R.styleable#TextView_textColorHint
Romain Guyd6a463a2009-05-21 23:10:10 -0700156 * @attr ref android.R.styleable#TextView_textAppearance
157 * @attr ref android.R.styleable#TextView_textColorLink
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800158 * @attr ref android.R.styleable#TextView_textSize
159 * @attr ref android.R.styleable#TextView_textScaleX
Raph Leviend570e892012-05-09 11:45:34 -0700160 * @attr ref android.R.styleable#TextView_fontFamily
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800161 * @attr ref android.R.styleable#TextView_typeface
162 * @attr ref android.R.styleable#TextView_textStyle
163 * @attr ref android.R.styleable#TextView_cursorVisible
164 * @attr ref android.R.styleable#TextView_maxLines
165 * @attr ref android.R.styleable#TextView_maxHeight
166 * @attr ref android.R.styleable#TextView_lines
167 * @attr ref android.R.styleable#TextView_height
168 * @attr ref android.R.styleable#TextView_minLines
169 * @attr ref android.R.styleable#TextView_minHeight
170 * @attr ref android.R.styleable#TextView_maxEms
171 * @attr ref android.R.styleable#TextView_maxWidth
172 * @attr ref android.R.styleable#TextView_ems
173 * @attr ref android.R.styleable#TextView_width
174 * @attr ref android.R.styleable#TextView_minEms
175 * @attr ref android.R.styleable#TextView_minWidth
176 * @attr ref android.R.styleable#TextView_gravity
177 * @attr ref android.R.styleable#TextView_scrollHorizontally
178 * @attr ref android.R.styleable#TextView_password
179 * @attr ref android.R.styleable#TextView_singleLine
180 * @attr ref android.R.styleable#TextView_selectAllOnFocus
181 * @attr ref android.R.styleable#TextView_includeFontPadding
182 * @attr ref android.R.styleable#TextView_maxLength
183 * @attr ref android.R.styleable#TextView_shadowColor
184 * @attr ref android.R.styleable#TextView_shadowDx
185 * @attr ref android.R.styleable#TextView_shadowDy
186 * @attr ref android.R.styleable#TextView_shadowRadius
187 * @attr ref android.R.styleable#TextView_autoLink
188 * @attr ref android.R.styleable#TextView_linksClickable
189 * @attr ref android.R.styleable#TextView_numeric
190 * @attr ref android.R.styleable#TextView_digits
191 * @attr ref android.R.styleable#TextView_phoneNumber
192 * @attr ref android.R.styleable#TextView_inputMethod
193 * @attr ref android.R.styleable#TextView_capitalize
194 * @attr ref android.R.styleable#TextView_autoText
195 * @attr ref android.R.styleable#TextView_editable
Romain Guyd6a463a2009-05-21 23:10:10 -0700196 * @attr ref android.R.styleable#TextView_freezesText
197 * @attr ref android.R.styleable#TextView_ellipsize
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800198 * @attr ref android.R.styleable#TextView_drawableTop
199 * @attr ref android.R.styleable#TextView_drawableBottom
200 * @attr ref android.R.styleable#TextView_drawableRight
201 * @attr ref android.R.styleable#TextView_drawableLeft
Fabrice Di Megliod1591092012-03-07 15:34:38 -0800202 * @attr ref android.R.styleable#TextView_drawableStart
203 * @attr ref android.R.styleable#TextView_drawableEnd
Romain Guyd6a463a2009-05-21 23:10:10 -0700204 * @attr ref android.R.styleable#TextView_drawablePadding
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800205 * @attr ref android.R.styleable#TextView_lineSpacingExtra
206 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
207 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
Romain Guyd6a463a2009-05-21 23:10:10 -0700208 * @attr ref android.R.styleable#TextView_inputType
209 * @attr ref android.R.styleable#TextView_imeOptions
210 * @attr ref android.R.styleable#TextView_privateImeOptions
211 * @attr ref android.R.styleable#TextView_imeActionLabel
212 * @attr ref android.R.styleable#TextView_imeActionId
213 * @attr ref android.R.styleable#TextView_editorExtras
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800214 */
215@RemoteView
216public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -0700217 static final String LOG_TAG = "TextView";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800218 static final boolean DEBUG_EXTRACT = false;
Gilles Debunneb7012e842011-02-24 15:40:38 -0800219
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800220 // Enum for the "typeface" XML parameter.
221 // TODO: How can we get this from the XML instead of hardcoding it here?
222 private static final int SANS = 1;
223 private static final int SERIF = 2;
224 private static final int MONOSPACE = 3;
225
226 // Bitfield for the "numeric" XML parameter.
227 // TODO: How can we get this from the XML instead of hardcoding it here?
228 private static final int SIGNED = 2;
229 private static final int DECIMAL = 4;
230
Adam Powell282e3772011-08-30 16:51:11 -0700231 /**
232 * Draw marquee text with fading edges as usual
233 */
234 private static final int MARQUEE_FADE_NORMAL = 0;
235
236 /**
237 * Draw marquee text as ellipsize end while inactive instead of with the fade.
238 * (Useful for devices where the fade can be expensive if overdone)
239 */
240 private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1;
241
242 /**
243 * Draw marquee text with fading edges because it is currently active/animating.
244 */
245 private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2;
246
Gilles Debunne60e21862012-01-30 15:04:14 -0800247 private static final int LINES = 1;
248 private static final int EMS = LINES;
249 private static final int PIXELS = 2;
250
251 private static final RectF TEMP_RECTF = new RectF();
Gilles Debunne60e21862012-01-30 15:04:14 -0800252
253 // XXX should be much larger
254 private static final int VERY_WIDE = 1024*1024;
Gilles Debunne60e21862012-01-30 15:04:14 -0800255 private static final int ANIMATED_SCROLL_GAP = 250;
256
257 private static final InputFilter[] NO_FILTERS = new InputFilter[0];
258 private static final Spanned EMPTY_SPANNED = new SpannedString("");
259
Gilles Debunne60e21862012-01-30 15:04:14 -0800260 private static final int CHANGE_WATCHER_PRIORITY = 100;
261
262 // New state used to change background based on whether this TextView is multiline.
263 private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
264
265 // System wide time for last cut or copy action.
Gilles Debunned88876a2012-03-16 17:34:04 -0700266 static long LAST_CUT_OR_COPY_TIME;
Gilles Debunne60e21862012-01-30 15:04:14 -0800267
Gilles Debunne60e21862012-01-30 15:04:14 -0800268 private ColorStateList mTextColor;
269 private ColorStateList mHintTextColor;
270 private ColorStateList mLinkTextColor;
271 private int mCurTextColor;
272 private int mCurHintTextColor;
273 private boolean mFreezesText;
274 private boolean mTemporaryDetach;
275 private boolean mDispatchTemporaryDetach;
276
277 private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
278 private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
279
280 private float mShadowRadius, mShadowDx, mShadowDy;
281
282 private boolean mPreDrawRegistered;
283
284 private TextUtils.TruncateAt mEllipsize;
285
286 static class Drawables {
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800287 final static int DRAWABLE_NONE = -1;
288 final static int DRAWABLE_RIGHT = 0;
289 final static int DRAWABLE_LEFT = 1;
290
Gilles Debunne60e21862012-01-30 15:04:14 -0800291 final Rect mCompoundRect = new Rect();
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800292
Gilles Debunne60e21862012-01-30 15:04:14 -0800293 Drawable mDrawableTop, mDrawableBottom, mDrawableLeft, mDrawableRight,
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800294 mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp;
295
Gilles Debunne60e21862012-01-30 15:04:14 -0800296 int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight,
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800297 mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp;
298
Gilles Debunne60e21862012-01-30 15:04:14 -0800299 int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight,
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800300 mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp;
301
Gilles Debunne60e21862012-01-30 15:04:14 -0800302 int mDrawablePadding;
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800303
304 int mDrawableSaved = DRAWABLE_NONE;
305
306 public void resolveWithLayoutDirection(int layoutDirection) {
307 switch(layoutDirection) {
308 case LAYOUT_DIRECTION_RTL:
309 if (mDrawableStart != null) {
310 mDrawableRight = mDrawableStart;
311
312 mDrawableSizeRight = mDrawableSizeStart;
313 mDrawableHeightRight = mDrawableHeightStart;
314 }
315 if (mDrawableEnd != null) {
316 mDrawableLeft = mDrawableEnd;
317
318 mDrawableSizeLeft = mDrawableSizeEnd;
319 mDrawableHeightLeft = mDrawableHeightEnd;
320 }
321 break;
322
323 case LAYOUT_DIRECTION_LTR:
324 default:
325 if (mDrawableStart != null) {
326 mDrawableLeft = mDrawableStart;
327
328 mDrawableSizeLeft = mDrawableSizeStart;
329 mDrawableHeightLeft = mDrawableHeightStart;
330 }
331 if (mDrawableEnd != null) {
332 mDrawableRight = mDrawableEnd;
333
334 mDrawableSizeRight = mDrawableSizeEnd;
335 mDrawableHeightRight = mDrawableHeightEnd;
336 }
337 break;
338 }
339 applyErrorDrawableIfNeeded(layoutDirection);
340 updateDrawablesLayoutDirection(layoutDirection);
341 }
342
343 private void updateDrawablesLayoutDirection(int layoutDirection) {
344 if (mDrawableLeft != null) {
345 mDrawableLeft.setLayoutDirection(layoutDirection);
346 }
347 if (mDrawableRight != null) {
348 mDrawableRight.setLayoutDirection(layoutDirection);
349 }
350 if (mDrawableTop != null) {
351 mDrawableTop.setLayoutDirection(layoutDirection);
352 }
353 if (mDrawableBottom != null) {
354 mDrawableBottom.setLayoutDirection(layoutDirection);
355 }
356 }
357
358 public void setErrorDrawable(Drawable dr, TextView tv) {
359 if (mDrawableError != dr && mDrawableError != null) {
360 mDrawableError.setCallback(null);
361 }
362 mDrawableError = dr;
363
364 final Rect compoundRect = mCompoundRect;
365 int[] state = tv.getDrawableState();
366
367 if (mDrawableError != null) {
368 mDrawableError.setState(state);
369 mDrawableError.copyBounds(compoundRect);
370 mDrawableError.setCallback(tv);
371 mDrawableSizeError = compoundRect.width();
372 mDrawableHeightError = compoundRect.height();
373 } else {
374 mDrawableSizeError = mDrawableHeightError = 0;
375 }
376 }
377
378 private void applyErrorDrawableIfNeeded(int layoutDirection) {
379 // first restore the initial state if needed
380 switch (mDrawableSaved) {
381 case DRAWABLE_LEFT:
382 mDrawableLeft = mDrawableTemp;
383 mDrawableSizeLeft = mDrawableSizeTemp;
384 mDrawableHeightLeft = mDrawableHeightTemp;
385 break;
386 case DRAWABLE_RIGHT:
387 mDrawableRight = mDrawableTemp;
388 mDrawableSizeRight = mDrawableSizeTemp;
389 mDrawableHeightRight = mDrawableHeightTemp;
390 break;
391 case DRAWABLE_NONE:
392 default:
393 }
394 // then, if needed, assign the Error drawable to the correct location
395 if (mDrawableError != null) {
396 switch(layoutDirection) {
397 case LAYOUT_DIRECTION_RTL:
398 mDrawableSaved = DRAWABLE_LEFT;
399
400 mDrawableTemp = mDrawableLeft;
401 mDrawableSizeTemp = mDrawableSizeLeft;
402 mDrawableHeightTemp = mDrawableHeightLeft;
403
404 mDrawableLeft = mDrawableError;
405 mDrawableSizeLeft = mDrawableSizeError;
406 mDrawableHeightLeft = mDrawableHeightError;
407 break;
408 case LAYOUT_DIRECTION_LTR:
409 default:
410 mDrawableSaved = DRAWABLE_RIGHT;
411
412 mDrawableTemp = mDrawableRight;
413 mDrawableSizeTemp = mDrawableSizeRight;
414 mDrawableHeightTemp = mDrawableHeightRight;
415
416 mDrawableRight = mDrawableError;
417 mDrawableSizeRight = mDrawableSizeError;
418 mDrawableHeightRight = mDrawableHeightError;
419 break;
420 }
421 }
422 }
Gilles Debunne60e21862012-01-30 15:04:14 -0800423 }
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800424
Gilles Debunned88876a2012-03-16 17:34:04 -0700425 Drawables mDrawables;
Gilles Debunne60e21862012-01-30 15:04:14 -0800426
427 private CharWrapper mCharWrapper;
428
429 private Marquee mMarquee;
430 private boolean mRestartMarquee;
431
432 private int mMarqueeRepeatLimit = 3;
433
434 // The alignment to pass to Layout, or null if not resolved.
435 private Layout.Alignment mLayoutAlignment;
Fabrice Di Megliofa1babd2012-09-04 19:11:25 -0700436 private int mResolvedTextAlignment;
Gilles Debunne60e21862012-01-30 15:04:14 -0800437
Fabrice Di Meglio1957d282012-10-25 17:42:39 -0700438 private int mLastLayoutDirection = -1;
Gilles Debunne60e21862012-01-30 15:04:14 -0800439
440 /**
441 * On some devices the fading edges add a performance penalty if used
442 * extensively in the same layout. This mode indicates how the marquee
443 * is currently being shown, if applicable. (mEllipsize will == MARQUEE)
444 */
445 private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
446
447 /**
448 * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores
449 * the layout that should be used when the mode switches.
450 */
451 private Layout mSavedMarqueeModeLayout;
452
453 @ViewDebug.ExportedProperty(category = "text")
454 private CharSequence mText;
455 private CharSequence mTransformed;
456 private BufferType mBufferType = BufferType.NORMAL;
457
458 private CharSequence mHint;
459 private Layout mHintLayout;
460
461 private MovementMethod mMovement;
462
463 private TransformationMethod mTransformation;
464 private boolean mAllowTransformationLengthChange;
465 private ChangeWatcher mChangeWatcher;
466
467 private ArrayList<TextWatcher> mListeners;
468
469 // display attributes
470 private final TextPaint mTextPaint;
471 private boolean mUserSetTextScaleX;
472 private Layout mLayout;
473
474 private int mGravity = Gravity.TOP | Gravity.START;
475 private boolean mHorizontallyScrolling;
476
477 private int mAutoLinkMask;
478 private boolean mLinksClickable = true;
479
480 private float mSpacingMult = 1.0f;
481 private float mSpacingAdd = 0.0f;
482
483 private int mMaximum = Integer.MAX_VALUE;
484 private int mMaxMode = LINES;
485 private int mMinimum = 0;
486 private int mMinMode = LINES;
487
488 private int mOldMaximum = mMaximum;
489 private int mOldMaxMode = mMaxMode;
490
491 private int mMaxWidth = Integer.MAX_VALUE;
492 private int mMaxWidthMode = PIXELS;
493 private int mMinWidth = 0;
494 private int mMinWidthMode = PIXELS;
495
496 private boolean mSingleLine;
497 private int mDesiredHeightAtMeasure = -1;
498 private boolean mIncludePad = true;
Raph Levienf5c1a872012-10-15 17:22:26 -0700499 private int mDeferScroll = -1;
Gilles Debunne60e21862012-01-30 15:04:14 -0800500
501 // tmp primitives, so we don't alloc them on each draw
502 private Rect mTempRect;
503 private long mLastScroll;
504 private Scroller mScroller;
505
506 private BoringLayout.Metrics mBoring, mHintBoring;
507 private BoringLayout mSavedLayout, mSavedHintLayout;
508
509 private TextDirectionHeuristic mTextDir;
510
511 private InputFilter[] mFilters = NO_FILTERS;
512
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +0900513 private volatile Locale mCurrentTextServicesLocaleCache;
514 private final ReentrantLock mCurrentTextServicesLocaleLock = new ReentrantLock();
515
Gilles Debunne83051b82012-02-24 20:01:13 -0800516 // It is possible to have a selection even when mEditor is null (programmatically set, like when
517 // a link is pressed). These highlight-related fields do not go in mEditor.
Gilles Debunned88876a2012-03-16 17:34:04 -0700518 int mHighlightColor = 0x6633B5E5;
Gilles Debunne83051b82012-02-24 20:01:13 -0800519 private Path mHighlightPath;
520 private final Paint mHighlightPaint;
521 private boolean mHighlightPathBogus = true;
522
Gilles Debunne60e21862012-01-30 15:04:14 -0800523 // Although these fields are specific to editable text, they are not added to Editor because
524 // they are defined by the TextView's style and are theme-dependent.
Gilles Debunned88876a2012-03-16 17:34:04 -0700525 int mCursorDrawableRes;
Gilles Debunne60e21862012-01-30 15:04:14 -0800526 // These four fields, could be moved to Editor, since we know their default values and we
527 // could condition the creation of the Editor to a non standard value. This is however
528 // brittle since the hardcoded values here (such as
529 // com.android.internal.R.drawable.text_select_handle_left) would have to be updated if the
530 // default style is modified.
Gilles Debunned88876a2012-03-16 17:34:04 -0700531 int mTextSelectHandleLeftRes;
532 int mTextSelectHandleRightRes;
533 int mTextSelectHandleRes;
534 int mTextEditSuggestionItemLayout;
Gilles Debunne60e21862012-01-30 15:04:14 -0800535
536 /**
537 * EditText specific data, created on demand when one of the Editor fields is used.
Gilles Debunne5fae9962012-05-08 14:53:20 -0700538 * See {@link #createEditorIfNeeded()}.
Gilles Debunne60e21862012-01-30 15:04:14 -0800539 */
540 private Editor mEditor;
541
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800542 /*
543 * Kick-start the font cache for the zygote process (to pay the cost of
544 * initializing freetype for our default font only once).
545 */
546 static {
547 Paint p = new Paint();
548 p.setAntiAlias(true);
549 // We don't care about the result, just the side-effect of measuring.
550 p.measureText("H");
551 }
552
553 /**
554 * Interface definition for a callback to be invoked when an action is
555 * performed on the editor.
556 */
557 public interface OnEditorActionListener {
558 /**
559 * Called when an action is being performed.
560 *
561 * @param v The view that was clicked.
562 * @param actionId Identifier of the action. This will be either the
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700563 * identifier you supplied, or {@link EditorInfo#IME_NULL
564 * EditorInfo.IME_NULL} if being called due to the enter key
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800565 * being pressed.
566 * @param event If triggered by an enter key, this is the event;
567 * otherwise, this is null.
568 * @return Return true if you have consumed the action, else false.
569 */
570 boolean onEditorAction(TextView v, int actionId, KeyEvent event);
571 }
Gilles Debunne21078e42011-08-02 10:22:35 -0700572
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800573 public TextView(Context context) {
574 this(context, null);
575 }
576
Gilles Debunnec1714022012-01-17 13:59:23 -0800577 public TextView(Context context, AttributeSet attrs) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800578 this(context, attrs, com.android.internal.R.attr.textViewStyle);
579 }
580
Gilles Debunnee15b3582010-06-16 15:17:21 -0700581 @SuppressWarnings("deprecation")
Gilles Debunnec1714022012-01-17 13:59:23 -0800582 public TextView(Context context, AttributeSet attrs, int defStyle) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800583 super(context, attrs, defStyle);
584 mText = "";
585
Christopher Tate1373a8e2011-11-10 19:59:13 -0800586 final Resources res = getResources();
587 final CompatibilityInfo compat = res.getCompatibilityInfo();
588
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800589 mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
Christopher Tate1373a8e2011-11-10 19:59:13 -0800590 mTextPaint.density = res.getDisplayMetrics().density;
591 mTextPaint.setCompatibilityScaling(compat.applicationScale);
Gilles Debunne8cbb4c62011-01-24 12:33:56 -0800592
Gilles Debunne83051b82012-02-24 20:01:13 -0800593 mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
594 mHighlightPaint.setCompatibilityScaling(compat.applicationScale);
595
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800596 mMovement = getDefaultMovementMethod();
Gilles Debunne60e21862012-01-30 15:04:14 -0800597
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800598 mTransformation = null;
599
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800600 int textColorHighlight = 0;
601 ColorStateList textColor = null;
602 ColorStateList textColorHint = null;
603 ColorStateList textColorLink = null;
604 int textSize = 15;
Raph Leviend570e892012-05-09 11:45:34 -0700605 String fontFamily = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800606 int typefaceIndex = -1;
607 int styleIndex = -1;
Adam Powell7f8f79a2011-07-07 18:35:54 -0700608 boolean allCaps = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800609
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700610 final Resources.Theme theme = context.getTheme();
611
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800612 /*
613 * Look the appearance up without checking first if it exists because
614 * almost every TextView has one and it greatly simplifies the logic
615 * to be able to parse the appearance first and then let specific tags
616 * for this View override it.
617 */
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700618 TypedArray a = theme.obtainStyledAttributes(
619 attrs, com.android.internal.R.styleable.TextViewAppearance, defStyle, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800620 TypedArray appearance = null;
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700621 int ap = a.getResourceId(
622 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);
623 a.recycle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800624 if (ap != -1) {
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700625 appearance = theme.obtainStyledAttributes(
626 ap, com.android.internal.R.styleable.TextAppearance);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800627 }
628 if (appearance != null) {
629 int n = appearance.getIndexCount();
630 for (int i = 0; i < n; i++) {
631 int attr = appearance.getIndex(i);
632
633 switch (attr) {
634 case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
635 textColorHighlight = appearance.getColor(attr, textColorHighlight);
636 break;
637
638 case com.android.internal.R.styleable.TextAppearance_textColor:
639 textColor = appearance.getColorStateList(attr);
640 break;
641
642 case com.android.internal.R.styleable.TextAppearance_textColorHint:
643 textColorHint = appearance.getColorStateList(attr);
644 break;
645
646 case com.android.internal.R.styleable.TextAppearance_textColorLink:
647 textColorLink = appearance.getColorStateList(attr);
648 break;
649
650 case com.android.internal.R.styleable.TextAppearance_textSize:
651 textSize = appearance.getDimensionPixelSize(attr, textSize);
652 break;
653
654 case com.android.internal.R.styleable.TextAppearance_typeface:
655 typefaceIndex = appearance.getInt(attr, -1);
656 break;
657
Raph Leviend570e892012-05-09 11:45:34 -0700658 case com.android.internal.R.styleable.TextAppearance_fontFamily:
659 fontFamily = appearance.getString(attr);
660 break;
661
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800662 case com.android.internal.R.styleable.TextAppearance_textStyle:
663 styleIndex = appearance.getInt(attr, -1);
664 break;
Adam Powell7f8f79a2011-07-07 18:35:54 -0700665
666 case com.android.internal.R.styleable.TextAppearance_textAllCaps:
667 allCaps = appearance.getBoolean(attr, false);
668 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800669 }
670 }
671
672 appearance.recycle();
673 }
674
675 boolean editable = getDefaultEditable();
676 CharSequence inputMethod = null;
677 int numeric = 0;
678 CharSequence digits = null;
679 boolean phone = false;
680 boolean autotext = false;
681 int autocap = -1;
682 int buffertype = 0;
683 boolean selectallonfocus = false;
684 Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -0700685 drawableBottom = null, drawableStart = null, drawableEnd = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800686 int drawablePadding = 0;
687 int ellipsize = -1;
Gilles Debunnef95449d2010-11-05 13:54:13 -0700688 boolean singleLine = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800689 int maxlength = -1;
690 CharSequence text = "";
Romain Guy4dc4f732009-06-19 15:16:40 -0700691 CharSequence hint = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800692 int shadowcolor = 0;
693 float dx = 0, dy = 0, r = 0;
694 boolean password = false;
695 int inputType = EditorInfo.TYPE_NULL;
696
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700697 a = theme.obtainStyledAttributes(
698 attrs, com.android.internal.R.styleable.TextView, defStyle, 0);
699
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800700 int n = a.getIndexCount();
701 for (int i = 0; i < n; i++) {
702 int attr = a.getIndex(i);
703
704 switch (attr) {
705 case com.android.internal.R.styleable.TextView_editable:
706 editable = a.getBoolean(attr, editable);
707 break;
708
709 case com.android.internal.R.styleable.TextView_inputMethod:
710 inputMethod = a.getText(attr);
711 break;
712
713 case com.android.internal.R.styleable.TextView_numeric:
714 numeric = a.getInt(attr, numeric);
715 break;
716
717 case com.android.internal.R.styleable.TextView_digits:
718 digits = a.getText(attr);
719 break;
720
721 case com.android.internal.R.styleable.TextView_phoneNumber:
722 phone = a.getBoolean(attr, phone);
723 break;
724
725 case com.android.internal.R.styleable.TextView_autoText:
726 autotext = a.getBoolean(attr, autotext);
727 break;
728
729 case com.android.internal.R.styleable.TextView_capitalize:
730 autocap = a.getInt(attr, autocap);
731 break;
732
733 case com.android.internal.R.styleable.TextView_bufferType:
734 buffertype = a.getInt(attr, buffertype);
735 break;
736
737 case com.android.internal.R.styleable.TextView_selectAllOnFocus:
738 selectallonfocus = a.getBoolean(attr, selectallonfocus);
739 break;
740
741 case com.android.internal.R.styleable.TextView_autoLink:
742 mAutoLinkMask = a.getInt(attr, 0);
743 break;
744
745 case com.android.internal.R.styleable.TextView_linksClickable:
746 mLinksClickable = a.getBoolean(attr, true);
747 break;
748
749 case com.android.internal.R.styleable.TextView_drawableLeft:
750 drawableLeft = a.getDrawable(attr);
751 break;
752
753 case com.android.internal.R.styleable.TextView_drawableTop:
754 drawableTop = a.getDrawable(attr);
755 break;
756
757 case com.android.internal.R.styleable.TextView_drawableRight:
758 drawableRight = a.getDrawable(attr);
759 break;
760
761 case com.android.internal.R.styleable.TextView_drawableBottom:
762 drawableBottom = a.getDrawable(attr);
763 break;
764
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -0700765 case com.android.internal.R.styleable.TextView_drawableStart:
766 drawableStart = a.getDrawable(attr);
767 break;
768
769 case com.android.internal.R.styleable.TextView_drawableEnd:
770 drawableEnd = a.getDrawable(attr);
771 break;
772
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800773 case com.android.internal.R.styleable.TextView_drawablePadding:
774 drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
775 break;
776
777 case com.android.internal.R.styleable.TextView_maxLines:
778 setMaxLines(a.getInt(attr, -1));
779 break;
780
781 case com.android.internal.R.styleable.TextView_maxHeight:
782 setMaxHeight(a.getDimensionPixelSize(attr, -1));
783 break;
784
785 case com.android.internal.R.styleable.TextView_lines:
786 setLines(a.getInt(attr, -1));
787 break;
788
789 case com.android.internal.R.styleable.TextView_height:
790 setHeight(a.getDimensionPixelSize(attr, -1));
791 break;
792
793 case com.android.internal.R.styleable.TextView_minLines:
794 setMinLines(a.getInt(attr, -1));
795 break;
796
797 case com.android.internal.R.styleable.TextView_minHeight:
798 setMinHeight(a.getDimensionPixelSize(attr, -1));
799 break;
800
801 case com.android.internal.R.styleable.TextView_maxEms:
802 setMaxEms(a.getInt(attr, -1));
803 break;
804
805 case com.android.internal.R.styleable.TextView_maxWidth:
806 setMaxWidth(a.getDimensionPixelSize(attr, -1));
807 break;
808
809 case com.android.internal.R.styleable.TextView_ems:
810 setEms(a.getInt(attr, -1));
811 break;
812
813 case com.android.internal.R.styleable.TextView_width:
814 setWidth(a.getDimensionPixelSize(attr, -1));
815 break;
816
817 case com.android.internal.R.styleable.TextView_minEms:
818 setMinEms(a.getInt(attr, -1));
819 break;
820
821 case com.android.internal.R.styleable.TextView_minWidth:
822 setMinWidth(a.getDimensionPixelSize(attr, -1));
823 break;
824
825 case com.android.internal.R.styleable.TextView_gravity:
826 setGravity(a.getInt(attr, -1));
827 break;
828
829 case com.android.internal.R.styleable.TextView_hint:
Romain Guy4dc4f732009-06-19 15:16:40 -0700830 hint = a.getText(attr);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800831 break;
832
833 case com.android.internal.R.styleable.TextView_text:
834 text = a.getText(attr);
835 break;
836
837 case com.android.internal.R.styleable.TextView_scrollHorizontally:
838 if (a.getBoolean(attr, false)) {
839 setHorizontallyScrolling(true);
840 }
841 break;
842
843 case com.android.internal.R.styleable.TextView_singleLine:
844 singleLine = a.getBoolean(attr, singleLine);
845 break;
846
847 case com.android.internal.R.styleable.TextView_ellipsize:
848 ellipsize = a.getInt(attr, ellipsize);
849 break;
850
851 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
852 setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
853 break;
854
855 case com.android.internal.R.styleable.TextView_includeFontPadding:
856 if (!a.getBoolean(attr, true)) {
857 setIncludeFontPadding(false);
858 }
859 break;
860
861 case com.android.internal.R.styleable.TextView_cursorVisible:
862 if (!a.getBoolean(attr, true)) {
863 setCursorVisible(false);
864 }
865 break;
866
867 case com.android.internal.R.styleable.TextView_maxLength:
868 maxlength = a.getInt(attr, -1);
869 break;
870
871 case com.android.internal.R.styleable.TextView_textScaleX:
872 setTextScaleX(a.getFloat(attr, 1.0f));
873 break;
874
875 case com.android.internal.R.styleable.TextView_freezesText:
876 mFreezesText = a.getBoolean(attr, false);
877 break;
878
879 case com.android.internal.R.styleable.TextView_shadowColor:
880 shadowcolor = a.getInt(attr, 0);
881 break;
882
883 case com.android.internal.R.styleable.TextView_shadowDx:
884 dx = a.getFloat(attr, 0);
885 break;
886
887 case com.android.internal.R.styleable.TextView_shadowDy:
888 dy = a.getFloat(attr, 0);
889 break;
890
891 case com.android.internal.R.styleable.TextView_shadowRadius:
892 r = a.getFloat(attr, 0);
893 break;
894
895 case com.android.internal.R.styleable.TextView_enabled:
896 setEnabled(a.getBoolean(attr, isEnabled()));
897 break;
898
899 case com.android.internal.R.styleable.TextView_textColorHighlight:
900 textColorHighlight = a.getColor(attr, textColorHighlight);
901 break;
902
903 case com.android.internal.R.styleable.TextView_textColor:
904 textColor = a.getColorStateList(attr);
905 break;
906
907 case com.android.internal.R.styleable.TextView_textColorHint:
908 textColorHint = a.getColorStateList(attr);
909 break;
910
911 case com.android.internal.R.styleable.TextView_textColorLink:
912 textColorLink = a.getColorStateList(attr);
913 break;
914
915 case com.android.internal.R.styleable.TextView_textSize:
916 textSize = a.getDimensionPixelSize(attr, textSize);
917 break;
918
919 case com.android.internal.R.styleable.TextView_typeface:
920 typefaceIndex = a.getInt(attr, typefaceIndex);
921 break;
922
923 case com.android.internal.R.styleable.TextView_textStyle:
924 styleIndex = a.getInt(attr, styleIndex);
925 break;
926
Raph Leviend570e892012-05-09 11:45:34 -0700927 case com.android.internal.R.styleable.TextView_fontFamily:
928 fontFamily = a.getString(attr);
929 break;
930
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800931 case com.android.internal.R.styleable.TextView_password:
932 password = a.getBoolean(attr, password);
933 break;
934
935 case com.android.internal.R.styleable.TextView_lineSpacingExtra:
936 mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
937 break;
938
939 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
940 mSpacingMult = a.getFloat(attr, mSpacingMult);
941 break;
942
943 case com.android.internal.R.styleable.TextView_inputType:
Gilles Debunne60e21862012-01-30 15:04:14 -0800944 inputType = a.getInt(attr, EditorInfo.TYPE_NULL);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800945 break;
946
947 case com.android.internal.R.styleable.TextView_imeOptions:
Gilles Debunne5fae9962012-05-08 14:53:20 -0700948 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -0700949 mEditor.createInputContentTypeIfNeeded();
950 mEditor.mInputContentType.imeOptions = a.getInt(attr,
951 mEditor.mInputContentType.imeOptions);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800952 break;
953
954 case com.android.internal.R.styleable.TextView_imeActionLabel:
Gilles Debunne5fae9962012-05-08 14:53:20 -0700955 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -0700956 mEditor.createInputContentTypeIfNeeded();
957 mEditor.mInputContentType.imeActionLabel = a.getText(attr);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800958 break;
959
960 case com.android.internal.R.styleable.TextView_imeActionId:
Gilles Debunne5fae9962012-05-08 14:53:20 -0700961 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -0700962 mEditor.createInputContentTypeIfNeeded();
963 mEditor.mInputContentType.imeActionId = a.getInt(attr,
964 mEditor.mInputContentType.imeActionId);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800965 break;
966
967 case com.android.internal.R.styleable.TextView_privateImeOptions:
968 setPrivateImeOptions(a.getString(attr));
969 break;
970
971 case com.android.internal.R.styleable.TextView_editorExtras:
972 try {
973 setInputExtras(a.getResourceId(attr, 0));
974 } catch (XmlPullParserException e) {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -0700975 Log.w(LOG_TAG, "Failure reading input extras", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800976 } catch (IOException e) {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -0700977 Log.w(LOG_TAG, "Failure reading input extras", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800978 }
979 break;
Adam Powellb08013c2010-09-16 16:28:11 -0700980
Gilles Debunnef75c97e2011-02-10 16:09:53 -0800981 case com.android.internal.R.styleable.TextView_textCursorDrawable:
982 mCursorDrawableRes = a.getResourceId(attr, 0);
983 break;
984
Adam Powellb08013c2010-09-16 16:28:11 -0700985 case com.android.internal.R.styleable.TextView_textSelectHandleLeft:
986 mTextSelectHandleLeftRes = a.getResourceId(attr, 0);
987 break;
988
989 case com.android.internal.R.styleable.TextView_textSelectHandleRight:
990 mTextSelectHandleRightRes = a.getResourceId(attr, 0);
991 break;
992
993 case com.android.internal.R.styleable.TextView_textSelectHandle:
994 mTextSelectHandleRes = a.getResourceId(attr, 0);
995 break;
Gilles Debunne7b9652b2010-10-26 16:27:12 -0700996
Gilles Debunne69340442011-03-31 13:37:51 -0700997 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout:
998 mTextEditSuggestionItemLayout = a.getResourceId(attr, 0);
999 break;
1000
Gilles Debunne86b9c782010-11-11 10:43:48 -08001001 case com.android.internal.R.styleable.TextView_textIsSelectable:
Gilles Debunne60e21862012-01-30 15:04:14 -08001002 setTextIsSelectable(a.getBoolean(attr, false));
Gilles Debunne86b9c782010-11-11 10:43:48 -08001003 break;
Gilles Debunnef3a135b2011-05-23 16:28:47 -07001004
Adam Powell7f8f79a2011-07-07 18:35:54 -07001005 case com.android.internal.R.styleable.TextView_textAllCaps:
1006 allCaps = a.getBoolean(attr, false);
1007 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001008 }
1009 }
1010 a.recycle();
1011
1012 BufferType bufferType = BufferType.EDITABLE;
1013
Gilles Debunned7483bf2010-11-10 10:47:45 -08001014 final int variation =
1015 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
1016 final boolean passwordInputType = variation
1017 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
1018 final boolean webPasswordInputType = variation
1019 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD);
Ken Wakasa82d731a2010-12-24 23:42:41 +09001020 final boolean numberPasswordInputType = variation
1021 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
Gilles Debunned7483bf2010-11-10 10:47:45 -08001022
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001023 if (inputMethod != null) {
Gilles Debunnee15b3582010-06-16 15:17:21 -07001024 Class<?> c;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001025
1026 try {
1027 c = Class.forName(inputMethod.toString());
1028 } catch (ClassNotFoundException ex) {
1029 throw new RuntimeException(ex);
1030 }
1031
1032 try {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001033 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001034 mEditor.mKeyListener = (KeyListener) c.newInstance();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001035 } catch (InstantiationException ex) {
1036 throw new RuntimeException(ex);
1037 } catch (IllegalAccessException ex) {
1038 throw new RuntimeException(ex);
1039 }
1040 try {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001041 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001042 ? inputType
Gilles Debunne2d373a12012-04-20 15:32:19 -07001043 : mEditor.mKeyListener.getInputType();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001044 } catch (IncompatibleClassChangeError e) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001045 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001046 }
1047 } else if (digits != null) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001048 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001049 mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString());
Dianne Hackborn7ed6ee52009-09-10 18:41:28 -07001050 // If no input type was specified, we will default to generic
1051 // text, since we can't tell the IME about the set of digits
1052 // that was selected.
Gilles Debunne2d373a12012-04-20 15:32:19 -07001053 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
Dianne Hackborn7ed6ee52009-09-10 18:41:28 -07001054 ? inputType : EditorInfo.TYPE_CLASS_TEXT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001055 } else if (inputType != EditorInfo.TYPE_NULL) {
1056 setInputType(inputType, true);
Gilles Debunne91a08cf2010-11-08 17:34:49 -08001057 // If set, the input type overrides what was set using the deprecated singleLine flag.
1058 singleLine = !isMultilineInputType(inputType);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001059 } else if (phone) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001060 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001061 mEditor.mKeyListener = DialerKeyListener.getInstance();
1062 mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001063 } else if (numeric != 0) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001064 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001065 mEditor.mKeyListener = DigitsKeyListener.getInstance((numeric & SIGNED) != 0,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001066 (numeric & DECIMAL) != 0);
1067 inputType = EditorInfo.TYPE_CLASS_NUMBER;
1068 if ((numeric & SIGNED) != 0) {
1069 inputType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED;
1070 }
1071 if ((numeric & DECIMAL) != 0) {
1072 inputType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL;
1073 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07001074 mEditor.mInputType = inputType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001075 } else if (autotext || autocap != -1) {
1076 TextKeyListener.Capitalize cap;
1077
1078 inputType = EditorInfo.TYPE_CLASS_TEXT;
Gilles Debunnef95449d2010-11-05 13:54:13 -07001079
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001080 switch (autocap) {
1081 case 1:
1082 cap = TextKeyListener.Capitalize.SENTENCES;
1083 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
1084 break;
1085
1086 case 2:
1087 cap = TextKeyListener.Capitalize.WORDS;
1088 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
1089 break;
1090
1091 case 3:
1092 cap = TextKeyListener.Capitalize.CHARACTERS;
1093 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
1094 break;
1095
1096 default:
1097 cap = TextKeyListener.Capitalize.NONE;
1098 break;
1099 }
1100
Gilles Debunne5fae9962012-05-08 14:53:20 -07001101 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001102 mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap);
1103 mEditor.mInputType = inputType;
Gilles Debunne60e21862012-01-30 15:04:14 -08001104 } else if (isTextSelectable()) {
Gilles Debunne86b9c782010-11-11 10:43:48 -08001105 // Prevent text changes from keyboard.
Gilles Debunne60e21862012-01-30 15:04:14 -08001106 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001107 mEditor.mKeyListener = null;
1108 mEditor.mInputType = EditorInfo.TYPE_NULL;
Gilles Debunne60e21862012-01-30 15:04:14 -08001109 }
Gilles Debunne86b9c782010-11-11 10:43:48 -08001110 bufferType = BufferType.SPANNABLE;
Gilles Debunne86b9c782010-11-11 10:43:48 -08001111 // So that selection can be changed using arrow keys and touch is handled.
1112 setMovementMethod(ArrowKeyMovementMethod.getInstance());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001113 } else if (editable) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001114 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001115 mEditor.mKeyListener = TextKeyListener.getInstance();
1116 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001117 } else {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001118 if (mEditor != null) mEditor.mKeyListener = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001119
1120 switch (buffertype) {
1121 case 0:
1122 bufferType = BufferType.NORMAL;
1123 break;
1124 case 1:
1125 bufferType = BufferType.SPANNABLE;
1126 break;
1127 case 2:
1128 bufferType = BufferType.EDITABLE;
1129 break;
1130 }
1131 }
1132
Gilles Debunne2d373a12012-04-20 15:32:19 -07001133 if (mEditor != null) mEditor.adjustInputType(password, passwordInputType,
1134 webPasswordInputType, numberPasswordInputType);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001135
1136 if (selectallonfocus) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001137 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001138 mEditor.mSelectAllOnFocus = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001139
1140 if (bufferType == BufferType.NORMAL)
1141 bufferType = BufferType.SPANNABLE;
1142 }
1143
1144 setCompoundDrawablesWithIntrinsicBounds(
1145 drawableLeft, drawableTop, drawableRight, drawableBottom);
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001146 setRelativeDrawablesIfNeeded(drawableStart, drawableEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001147 setCompoundDrawablePadding(drawablePadding);
1148
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08001149 // Same as setSingleLine(), but make sure the transformation method and the maximum number
Gilles Debunne066460f2010-12-15 17:31:51 -08001150 // of lines of height are unchanged for multi-line TextViews.
Gilles Debunned7483bf2010-11-10 10:47:45 -08001151 setInputTypeSingleLine(singleLine);
Gilles Debunne066460f2010-12-15 17:31:51 -08001152 applySingleLine(singleLine, singleLine, singleLine);
Gilles Debunned7483bf2010-11-10 10:47:45 -08001153
Gilles Debunne60e21862012-01-30 15:04:14 -08001154 if (singleLine && getKeyListener() == null && ellipsize < 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001155 ellipsize = 3; // END
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001156 }
1157
1158 switch (ellipsize) {
1159 case 1:
1160 setEllipsize(TextUtils.TruncateAt.START);
1161 break;
1162 case 2:
1163 setEllipsize(TextUtils.TruncateAt.MIDDLE);
1164 break;
1165 case 3:
1166 setEllipsize(TextUtils.TruncateAt.END);
1167 break;
1168 case 4:
Adam Powell282e3772011-08-30 16:51:11 -07001169 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) {
1170 setHorizontalFadingEdgeEnabled(true);
1171 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
1172 } else {
1173 setHorizontalFadingEdgeEnabled(false);
1174 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
1175 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001176 setEllipsize(TextUtils.TruncateAt.MARQUEE);
1177 break;
1178 }
1179
1180 setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000));
1181 setHintTextColor(textColorHint);
1182 setLinkTextColor(textColorLink);
1183 if (textColorHighlight != 0) {
1184 setHighlightColor(textColorHighlight);
1185 }
1186 setRawTextSize(textSize);
1187
Adam Powell7f8f79a2011-07-07 18:35:54 -07001188 if (allCaps) {
1189 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
1190 }
1191
Ken Wakasa82d731a2010-12-24 23:42:41 +09001192 if (password || passwordInputType || webPasswordInputType || numberPasswordInputType) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001193 setTransformationMethod(PasswordTransformationMethod.getInstance());
1194 typefaceIndex = MONOSPACE;
Gilles Debunne2d373a12012-04-20 15:32:19 -07001195 } else if (mEditor != null &&
1196 (mEditor.mInputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
Gilles Debunned7483bf2010-11-10 10:47:45 -08001197 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08001198 typefaceIndex = MONOSPACE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001199 }
1200
Raph Leviend570e892012-05-09 11:45:34 -07001201 setTypefaceFromAttrs(fontFamily, typefaceIndex, styleIndex);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001202
1203 if (shadowcolor != 0) {
1204 setShadowLayer(r, dx, dy, shadowcolor);
1205 }
1206
1207 if (maxlength >= 0) {
1208 setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
1209 } else {
1210 setFilters(NO_FILTERS);
1211 }
1212
1213 setText(text, bufferType);
Romain Guy4dc4f732009-06-19 15:16:40 -07001214 if (hint != null) setHint(hint);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001215
1216 /*
1217 * Views are not normally focusable unless specified to be.
1218 * However, TextViews that have input or movement methods *are*
1219 * focusable by default.
1220 */
1221 a = context.obtainStyledAttributes(attrs,
1222 com.android.internal.R.styleable.View,
1223 defStyle, 0);
1224
Gilles Debunne60e21862012-01-30 15:04:14 -08001225 boolean focusable = mMovement != null || getKeyListener() != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001226 boolean clickable = focusable;
1227 boolean longClickable = focusable;
1228
1229 n = a.getIndexCount();
1230 for (int i = 0; i < n; i++) {
1231 int attr = a.getIndex(i);
1232
1233 switch (attr) {
1234 case com.android.internal.R.styleable.View_focusable:
1235 focusable = a.getBoolean(attr, focusable);
1236 break;
1237
1238 case com.android.internal.R.styleable.View_clickable:
1239 clickable = a.getBoolean(attr, clickable);
1240 break;
1241
1242 case com.android.internal.R.styleable.View_longClickable:
1243 longClickable = a.getBoolean(attr, longClickable);
1244 break;
1245 }
1246 }
1247 a.recycle();
1248
1249 setFocusable(focusable);
1250 setClickable(clickable);
1251 setLongClickable(longClickable);
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07001252
Gilles Debunned88876a2012-03-16 17:34:04 -07001253 if (mEditor != null) mEditor.prepareCursorControllers();
Svetoslav Ganov42138042012-03-20 11:51:39 -07001254
1255 // If not explicitly specified this view is important for accessibility.
1256 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
1257 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
1258 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001259 }
1260
Raph Leviend570e892012-05-09 11:45:34 -07001261 private void setTypefaceFromAttrs(String familyName, int typefaceIndex, int styleIndex) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001262 Typeface tf = null;
Raph Leviend570e892012-05-09 11:45:34 -07001263 if (familyName != null) {
1264 tf = Typeface.create(familyName, styleIndex);
1265 if (tf != null) {
1266 setTypeface(tf);
1267 return;
1268 }
1269 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001270 switch (typefaceIndex) {
1271 case SANS:
1272 tf = Typeface.SANS_SERIF;
1273 break;
1274
1275 case SERIF:
1276 tf = Typeface.SERIF;
1277 break;
1278
1279 case MONOSPACE:
1280 tf = Typeface.MONOSPACE;
1281 break;
1282 }
1283
1284 setTypeface(tf, styleIndex);
1285 }
1286
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001287 private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) {
1288 boolean hasRelativeDrawables = (start != null) || (end != null);
1289 if (hasRelativeDrawables) {
1290 Drawables dr = mDrawables;
1291 if (dr == null) {
1292 mDrawables = dr = new Drawables();
1293 }
1294 final Rect compoundRect = dr.mCompoundRect;
1295 int[] state = getDrawableState();
1296 if (start != null) {
1297 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
1298 start.setState(state);
1299 start.copyBounds(compoundRect);
1300 start.setCallback(this);
1301
1302 dr.mDrawableStart = start;
1303 dr.mDrawableSizeStart = compoundRect.width();
1304 dr.mDrawableHeightStart = compoundRect.height();
1305 } else {
1306 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
1307 }
1308 if (end != null) {
1309 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
1310 end.setState(state);
1311 end.copyBounds(compoundRect);
1312 end.setCallback(this);
1313
1314 dr.mDrawableEnd = end;
1315 dr.mDrawableSizeEnd = compoundRect.width();
1316 dr.mDrawableHeightEnd = compoundRect.height();
1317 } else {
1318 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
1319 }
1320 }
1321 }
1322
Janos Levai042856c2010-10-15 02:53:58 +03001323 @Override
1324 public void setEnabled(boolean enabled) {
1325 if (enabled == isEnabled()) {
1326 return;
1327 }
1328
1329 if (!enabled) {
1330 // Hide the soft input if the currently active TextView is disabled
1331 InputMethodManager imm = InputMethodManager.peekInstance();
1332 if (imm != null && imm.isActive(this)) {
1333 imm.hideSoftInputFromWindow(getWindowToken(), 0);
1334 }
1335 }
Gilles Debunne545c4d42011-11-29 10:37:15 -08001336
Janos Levai042856c2010-10-15 02:53:58 +03001337 super.setEnabled(enabled);
Gilles Debunne545c4d42011-11-29 10:37:15 -08001338
Dianne Hackbornbc823852011-09-18 17:19:50 -07001339 if (enabled) {
1340 // Make sure IME is updated with current editor info.
1341 InputMethodManager imm = InputMethodManager.peekInstance();
1342 if (imm != null) imm.restartInput(this);
1343 }
Mark Wagnerf8185112011-10-25 16:33:41 -07001344
Gilles Debunne33b7de852012-03-12 11:57:48 -07001345 // Will change text color
Gilles Debunned88876a2012-03-16 17:34:04 -07001346 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001347 mEditor.invalidateTextDisplayList();
1348 mEditor.prepareCursorControllers();
Gilles Debunne545c4d42011-11-29 10:37:15 -08001349
Gilles Debunned88876a2012-03-16 17:34:04 -07001350 // start or stop the cursor blinking as appropriate
Gilles Debunne2d373a12012-04-20 15:32:19 -07001351 mEditor.makeBlink();
Gilles Debunned88876a2012-03-16 17:34:04 -07001352 }
Janos Levai042856c2010-10-15 02:53:58 +03001353 }
1354
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001355 /**
1356 * Sets the typeface and style in which the text should be displayed,
1357 * and turns on the fake bold and italic bits in the Paint if the
1358 * Typeface that you provided does not have all the bits in the
1359 * style that you specified.
1360 *
1361 * @attr ref android.R.styleable#TextView_typeface
1362 * @attr ref android.R.styleable#TextView_textStyle
1363 */
1364 public void setTypeface(Typeface tf, int style) {
1365 if (style > 0) {
1366 if (tf == null) {
1367 tf = Typeface.defaultFromStyle(style);
1368 } else {
1369 tf = Typeface.create(tf, style);
1370 }
1371
1372 setTypeface(tf);
1373 // now compute what (if any) algorithmic styling is needed
1374 int typefaceStyle = tf != null ? tf.getStyle() : 0;
1375 int need = style & ~typefaceStyle;
1376 mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
1377 mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
1378 } else {
Victoria Leaseaa0980a2012-06-11 14:46:04 -07001379 mTextPaint.setFakeBoldText(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001380 mTextPaint.setTextSkewX(0);
1381 setTypeface(tf);
1382 }
1383 }
1384
1385 /**
1386 * Subclasses override this to specify that they have a KeyListener
1387 * by default even if not specifically called for in the XML options.
1388 */
1389 protected boolean getDefaultEditable() {
1390 return false;
1391 }
1392
1393 /**
1394 * Subclasses override this to specify a default movement method.
1395 */
1396 protected MovementMethod getDefaultMovementMethod() {
1397 return null;
1398 }
1399
1400 /**
1401 * Return the text the TextView is displaying. If setText() was called with
1402 * an argument of BufferType.SPANNABLE or BufferType.EDITABLE, you can cast
1403 * the return value from this method to Spannable or Editable, respectively.
1404 *
1405 * Note: The content of the return value should not be modified. If you want
1406 * a modifiable one, you should make your own copy first.
Gilles Debunnef03acef2012-04-30 19:26:19 -07001407 *
1408 * @attr ref android.R.styleable#TextView_text
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001409 */
1410 @ViewDebug.CapturedViewProperty
1411 public CharSequence getText() {
1412 return mText;
1413 }
1414
1415 /**
1416 * Returns the length, in characters, of the text managed by this TextView
1417 */
1418 public int length() {
1419 return mText.length();
1420 }
1421
1422 /**
1423 * Return the text the TextView is displaying as an Editable object. If
1424 * the text is not editable, null is returned.
1425 *
1426 * @see #getText
1427 */
1428 public Editable getEditableText() {
1429 return (mText instanceof Editable) ? (Editable)mText : null;
1430 }
1431
1432 /**
1433 * @return the height of one standard line in pixels. Note that markup
1434 * within the text can cause individual lines to be taller or shorter
1435 * than this height, and the layout may contain additional first-
1436 * or last-line padding.
1437 */
1438 public int getLineHeight() {
Gilles Debunne96e6b8b2010-12-14 13:43:45 -08001439 return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001440 }
1441
1442 /**
1443 * @return the Layout that is currently being used to display the text.
1444 * This can be null if the text or width has recently changes.
1445 */
1446 public final Layout getLayout() {
1447 return mLayout;
1448 }
1449
1450 /**
Fabrice Di Meglio0ed59fa2012-05-29 20:32:51 -07001451 * @return the Layout that is currently being used to display the hint text.
1452 * This can be null.
1453 */
1454 final Layout getHintLayout() {
1455 return mHintLayout;
1456 }
1457
1458 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001459 * @return the current key listener for this TextView.
1460 * This will frequently be null for non-EditText TextViews.
Gilles Debunnef03acef2012-04-30 19:26:19 -07001461 *
1462 * @attr ref android.R.styleable#TextView_numeric
1463 * @attr ref android.R.styleable#TextView_digits
1464 * @attr ref android.R.styleable#TextView_phoneNumber
1465 * @attr ref android.R.styleable#TextView_inputMethod
1466 * @attr ref android.R.styleable#TextView_capitalize
1467 * @attr ref android.R.styleable#TextView_autoText
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001468 */
1469 public final KeyListener getKeyListener() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001470 return mEditor == null ? null : mEditor.mKeyListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001471 }
1472
1473 /**
1474 * Sets the key listener to be used with this TextView. This can be null
1475 * to disallow user input. Note that this method has significant and
1476 * subtle interactions with soft keyboards and other input method:
1477 * see {@link KeyListener#getInputType() KeyListener.getContentType()}
1478 * for important details. Calling this method will replace the current
1479 * content type of the text view with the content type returned by the
1480 * key listener.
1481 * <p>
1482 * Be warned that if you want a TextView with a key listener or movement
1483 * method not to be focusable, or if you want a TextView without a
1484 * key listener or movement method to be focusable, you must call
1485 * {@link #setFocusable} again after calling this to get the focusability
1486 * back the way you want it.
1487 *
1488 * @attr ref android.R.styleable#TextView_numeric
1489 * @attr ref android.R.styleable#TextView_digits
1490 * @attr ref android.R.styleable#TextView_phoneNumber
1491 * @attr ref android.R.styleable#TextView_inputMethod
1492 * @attr ref android.R.styleable#TextView_capitalize
1493 * @attr ref android.R.styleable#TextView_autoText
1494 */
1495 public void setKeyListener(KeyListener input) {
1496 setKeyListenerOnly(input);
1497 fixFocusableAndClickableSettings();
1498
1499 if (input != null) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001500 createEditorIfNeeded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001501 try {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001502 mEditor.mInputType = mEditor.mKeyListener.getInputType();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001503 } catch (IncompatibleClassChangeError e) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001504 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001505 }
Gilles Debunned7483bf2010-11-10 10:47:45 -08001506 // Change inputType, without affecting transformation.
1507 // No need to applySingleLine since mSingleLine is unchanged.
1508 setInputTypeSingleLine(mSingleLine);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001509 } else {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001510 if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001511 }
1512
1513 InputMethodManager imm = InputMethodManager.peekInstance();
1514 if (imm != null) imm.restartInput(this);
1515 }
1516
1517 private void setKeyListenerOnly(KeyListener input) {
Gilles Debunne60e21862012-01-30 15:04:14 -08001518 if (mEditor == null && input == null) return; // null is the default value
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001519
Gilles Debunne5fae9962012-05-08 14:53:20 -07001520 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001521 if (mEditor.mKeyListener != input) {
1522 mEditor.mKeyListener = input;
Gilles Debunne60e21862012-01-30 15:04:14 -08001523 if (input != null && !(mText instanceof Editable)) {
1524 setText(mText);
1525 }
1526
1527 setFilters((Editable) mText, mFilters);
1528 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001529 }
1530
1531 /**
1532 * @return the movement method being used for this TextView.
1533 * This will frequently be null for non-EditText TextViews.
1534 */
1535 public final MovementMethod getMovementMethod() {
1536 return mMovement;
1537 }
1538
1539 /**
1540 * Sets the movement method (arrow key handler) to be used for
1541 * this TextView. This can be null to disallow using the arrow keys
1542 * to move the cursor or scroll the view.
1543 * <p>
1544 * Be warned that if you want a TextView with a key listener or movement
1545 * method not to be focusable, or if you want a TextView without a
1546 * key listener or movement method to be focusable, you must call
1547 * {@link #setFocusable} again after calling this to get the focusability
1548 * back the way you want it.
1549 */
1550 public final void setMovementMethod(MovementMethod movement) {
Gilles Debunne60e21862012-01-30 15:04:14 -08001551 if (mMovement != movement) {
1552 mMovement = movement;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001553
Gilles Debunne60e21862012-01-30 15:04:14 -08001554 if (movement != null && !(mText instanceof Spannable)) {
1555 setText(mText);
1556 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001557
Gilles Debunne60e21862012-01-30 15:04:14 -08001558 fixFocusableAndClickableSettings();
Gilles Debunnef788a9f2010-07-22 10:17:23 -07001559
Gilles Debunne2d373a12012-04-20 15:32:19 -07001560 // SelectionModifierCursorController depends on textCanBeSelected, which depends on
1561 // mMovement
1562 if (mEditor != null) mEditor.prepareCursorControllers();
Gilles Debunne60e21862012-01-30 15:04:14 -08001563 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001564 }
1565
1566 private void fixFocusableAndClickableSettings() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001567 if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001568 setFocusable(true);
1569 setClickable(true);
1570 setLongClickable(true);
1571 } else {
1572 setFocusable(false);
1573 setClickable(false);
1574 setLongClickable(false);
1575 }
1576 }
1577
1578 /**
1579 * @return the current transformation method for this TextView.
1580 * This will frequently be null except for single-line and password
1581 * fields.
Gilles Debunnef03acef2012-04-30 19:26:19 -07001582 *
1583 * @attr ref android.R.styleable#TextView_password
1584 * @attr ref android.R.styleable#TextView_singleLine
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001585 */
1586 public final TransformationMethod getTransformationMethod() {
1587 return mTransformation;
1588 }
1589
1590 /**
1591 * Sets the transformation that is applied to the text that this
1592 * TextView is displaying.
1593 *
1594 * @attr ref android.R.styleable#TextView_password
1595 * @attr ref android.R.styleable#TextView_singleLine
1596 */
1597 public final void setTransformationMethod(TransformationMethod method) {
1598 if (method == mTransformation) {
1599 // Avoid the setText() below if the transformation is
1600 // the same.
1601 return;
1602 }
1603 if (mTransformation != null) {
1604 if (mText instanceof Spannable) {
1605 ((Spannable) mText).removeSpan(mTransformation);
1606 }
1607 }
1608
1609 mTransformation = method;
1610
Adam Powell7f8f79a2011-07-07 18:35:54 -07001611 if (method instanceof TransformationMethod2) {
1612 TransformationMethod2 method2 = (TransformationMethod2) method;
Gilles Debunne60e21862012-01-30 15:04:14 -08001613 mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable);
Adam Powell7f8f79a2011-07-07 18:35:54 -07001614 method2.setLengthChangesAllowed(mAllowTransformationLengthChange);
1615 } else {
1616 mAllowTransformationLengthChange = false;
1617 }
1618
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001619 setText(mText);
Svetoslav Ganovc406be92012-05-11 16:12:32 -07001620
1621 if (hasPasswordTransformationMethod()) {
1622 notifyAccessibilityStateChanged();
1623 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001624 }
1625
1626 /**
1627 * Returns the top padding of the view, plus space for the top
1628 * Drawable if any.
1629 */
1630 public int getCompoundPaddingTop() {
1631 final Drawables dr = mDrawables;
1632 if (dr == null || dr.mDrawableTop == null) {
1633 return mPaddingTop;
1634 } else {
1635 return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
1636 }
1637 }
1638
1639 /**
1640 * Returns the bottom padding of the view, plus space for the bottom
1641 * Drawable if any.
1642 */
1643 public int getCompoundPaddingBottom() {
1644 final Drawables dr = mDrawables;
1645 if (dr == null || dr.mDrawableBottom == null) {
1646 return mPaddingBottom;
1647 } else {
1648 return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
1649 }
1650 }
1651
1652 /**
1653 * Returns the left padding of the view, plus space for the left
1654 * Drawable if any.
1655 */
1656 public int getCompoundPaddingLeft() {
1657 final Drawables dr = mDrawables;
1658 if (dr == null || dr.mDrawableLeft == null) {
1659 return mPaddingLeft;
1660 } else {
1661 return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
1662 }
1663 }
1664
1665 /**
1666 * Returns the right padding of the view, plus space for the right
1667 * Drawable if any.
1668 */
1669 public int getCompoundPaddingRight() {
1670 final Drawables dr = mDrawables;
1671 if (dr == null || dr.mDrawableRight == null) {
1672 return mPaddingRight;
1673 } else {
1674 return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
1675 }
1676 }
1677
1678 /**
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001679 * Returns the start padding of the view, plus space for the start
1680 * Drawable if any.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001681 */
1682 public int getCompoundPaddingStart() {
1683 resolveDrawables();
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07001684 switch(getLayoutDirection()) {
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001685 default:
1686 case LAYOUT_DIRECTION_LTR:
1687 return getCompoundPaddingLeft();
1688 case LAYOUT_DIRECTION_RTL:
1689 return getCompoundPaddingRight();
1690 }
1691 }
1692
1693 /**
1694 * Returns the end padding of the view, plus space for the end
1695 * Drawable if any.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001696 */
1697 public int getCompoundPaddingEnd() {
1698 resolveDrawables();
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07001699 switch(getLayoutDirection()) {
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001700 default:
1701 case LAYOUT_DIRECTION_LTR:
1702 return getCompoundPaddingRight();
1703 case LAYOUT_DIRECTION_RTL:
1704 return getCompoundPaddingLeft();
1705 }
1706 }
1707
1708 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001709 * Returns the extended top padding of the view, including both the
1710 * top Drawable if any and any extra space to keep more than maxLines
1711 * of text from showing. It is only valid to call this after measuring.
1712 */
1713 public int getExtendedPaddingTop() {
1714 if (mMaxMode != LINES) {
1715 return getCompoundPaddingTop();
1716 }
1717
1718 if (mLayout.getLineCount() <= mMaximum) {
1719 return getCompoundPaddingTop();
1720 }
1721
1722 int top = getCompoundPaddingTop();
1723 int bottom = getCompoundPaddingBottom();
1724 int viewht = getHeight() - top - bottom;
1725 int layoutht = mLayout.getLineTop(mMaximum);
1726
1727 if (layoutht >= viewht) {
1728 return top;
1729 }
1730
1731 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
1732 if (gravity == Gravity.TOP) {
1733 return top;
1734 } else if (gravity == Gravity.BOTTOM) {
1735 return top + viewht - layoutht;
1736 } else { // (gravity == Gravity.CENTER_VERTICAL)
1737 return top + (viewht - layoutht) / 2;
1738 }
1739 }
1740
1741 /**
1742 * Returns the extended bottom padding of the view, including both the
1743 * bottom Drawable if any and any extra space to keep more than maxLines
1744 * of text from showing. It is only valid to call this after measuring.
1745 */
1746 public int getExtendedPaddingBottom() {
1747 if (mMaxMode != LINES) {
1748 return getCompoundPaddingBottom();
1749 }
1750
1751 if (mLayout.getLineCount() <= mMaximum) {
1752 return getCompoundPaddingBottom();
1753 }
1754
1755 int top = getCompoundPaddingTop();
1756 int bottom = getCompoundPaddingBottom();
1757 int viewht = getHeight() - top - bottom;
1758 int layoutht = mLayout.getLineTop(mMaximum);
1759
1760 if (layoutht >= viewht) {
1761 return bottom;
1762 }
1763
1764 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
1765 if (gravity == Gravity.TOP) {
1766 return bottom + viewht - layoutht;
1767 } else if (gravity == Gravity.BOTTOM) {
1768 return bottom;
1769 } else { // (gravity == Gravity.CENTER_VERTICAL)
1770 return bottom + (viewht - layoutht) / 2;
1771 }
1772 }
1773
1774 /**
1775 * Returns the total left padding of the view, including the left
1776 * Drawable if any.
1777 */
1778 public int getTotalPaddingLeft() {
1779 return getCompoundPaddingLeft();
1780 }
1781
1782 /**
1783 * Returns the total right padding of the view, including the right
1784 * Drawable if any.
1785 */
1786 public int getTotalPaddingRight() {
1787 return getCompoundPaddingRight();
1788 }
1789
1790 /**
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001791 * Returns the total start padding of the view, including the start
1792 * Drawable if any.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001793 */
1794 public int getTotalPaddingStart() {
1795 return getCompoundPaddingStart();
1796 }
1797
1798 /**
1799 * Returns the total end padding of the view, including the end
1800 * Drawable if any.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001801 */
1802 public int getTotalPaddingEnd() {
1803 return getCompoundPaddingEnd();
1804 }
1805
1806 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001807 * Returns the total top padding of the view, including the top
1808 * Drawable if any, the extra space to keep more than maxLines
1809 * from showing, and the vertical offset for gravity, if any.
1810 */
1811 public int getTotalPaddingTop() {
1812 return getExtendedPaddingTop() + getVerticalOffset(true);
1813 }
1814
1815 /**
1816 * Returns the total bottom padding of the view, including the bottom
1817 * Drawable if any, the extra space to keep more than maxLines
1818 * from showing, and the vertical offset for gravity, if any.
1819 */
1820 public int getTotalPaddingBottom() {
1821 return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
1822 }
1823
1824 /**
1825 * Sets the Drawables (if any) to appear to the left of, above,
1826 * to the right of, and below the text. Use null if you do not
1827 * want a Drawable there. The Drawables must already have had
1828 * {@link Drawable#setBounds} called.
1829 *
1830 * @attr ref android.R.styleable#TextView_drawableLeft
1831 * @attr ref android.R.styleable#TextView_drawableTop
1832 * @attr ref android.R.styleable#TextView_drawableRight
1833 * @attr ref android.R.styleable#TextView_drawableBottom
1834 */
1835 public void setCompoundDrawables(Drawable left, Drawable top,
1836 Drawable right, Drawable bottom) {
1837 Drawables dr = mDrawables;
1838
1839 final boolean drawables = left != null || top != null
1840 || right != null || bottom != null;
1841
1842 if (!drawables) {
1843 // Clearing drawables... can we free the data structure?
1844 if (dr != null) {
1845 if (dr.mDrawablePadding == 0) {
1846 mDrawables = null;
1847 } else {
1848 // We need to retain the last set padding, so just clear
1849 // out all of the fields in the existing structure.
Romain Guy48540eb2009-05-19 16:44:57 -07001850 if (dr.mDrawableLeft != null) dr.mDrawableLeft.setCallback(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001851 dr.mDrawableLeft = null;
Romain Guy48540eb2009-05-19 16:44:57 -07001852 if (dr.mDrawableTop != null) dr.mDrawableTop.setCallback(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001853 dr.mDrawableTop = null;
Romain Guy48540eb2009-05-19 16:44:57 -07001854 if (dr.mDrawableRight != null) dr.mDrawableRight.setCallback(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001855 dr.mDrawableRight = null;
Romain Guy48540eb2009-05-19 16:44:57 -07001856 if (dr.mDrawableBottom != null) dr.mDrawableBottom.setCallback(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001857 dr.mDrawableBottom = null;
1858 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
1859 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
1860 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
1861 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
1862 }
1863 }
1864 } else {
1865 if (dr == null) {
1866 mDrawables = dr = new Drawables();
1867 }
1868
Romain Guy48540eb2009-05-19 16:44:57 -07001869 if (dr.mDrawableLeft != left && dr.mDrawableLeft != null) {
1870 dr.mDrawableLeft.setCallback(null);
1871 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001872 dr.mDrawableLeft = left;
Romain Guy8e618e52010-03-08 12:18:20 -08001873
1874 if (dr.mDrawableTop != top && dr.mDrawableTop != null) {
Romain Guy48540eb2009-05-19 16:44:57 -07001875 dr.mDrawableTop.setCallback(null);
1876 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001877 dr.mDrawableTop = top;
Romain Guy8e618e52010-03-08 12:18:20 -08001878
1879 if (dr.mDrawableRight != right && dr.mDrawableRight != null) {
Romain Guy48540eb2009-05-19 16:44:57 -07001880 dr.mDrawableRight.setCallback(null);
1881 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001882 dr.mDrawableRight = right;
Romain Guy8e618e52010-03-08 12:18:20 -08001883
1884 if (dr.mDrawableBottom != bottom && dr.mDrawableBottom != null) {
Romain Guy48540eb2009-05-19 16:44:57 -07001885 dr.mDrawableBottom.setCallback(null);
1886 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001887 dr.mDrawableBottom = bottom;
1888
1889 final Rect compoundRect = dr.mCompoundRect;
Romain Guy48540eb2009-05-19 16:44:57 -07001890 int[] state;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001891
1892 state = getDrawableState();
1893
1894 if (left != null) {
1895 left.setState(state);
1896 left.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07001897 left.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001898 dr.mDrawableSizeLeft = compoundRect.width();
1899 dr.mDrawableHeightLeft = compoundRect.height();
1900 } else {
1901 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
1902 }
1903
1904 if (right != null) {
1905 right.setState(state);
1906 right.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07001907 right.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001908 dr.mDrawableSizeRight = compoundRect.width();
1909 dr.mDrawableHeightRight = compoundRect.height();
1910 } else {
1911 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
1912 }
1913
1914 if (top != null) {
1915 top.setState(state);
1916 top.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07001917 top.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001918 dr.mDrawableSizeTop = compoundRect.height();
1919 dr.mDrawableWidthTop = compoundRect.width();
1920 } else {
1921 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
1922 }
1923
1924 if (bottom != null) {
1925 bottom.setState(state);
1926 bottom.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07001927 bottom.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001928 dr.mDrawableSizeBottom = compoundRect.height();
1929 dr.mDrawableWidthBottom = compoundRect.width();
1930 } else {
1931 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
1932 }
1933 }
1934
1935 invalidate();
1936 requestLayout();
1937 }
1938
1939 /**
1940 * Sets the Drawables (if any) to appear to the left of, above,
1941 * to the right of, and below the text. Use 0 if you do not
1942 * want a Drawable there. The Drawables' bounds will be set to
1943 * their intrinsic bounds.
1944 *
1945 * @param left Resource identifier of the left Drawable.
1946 * @param top Resource identifier of the top Drawable.
1947 * @param right Resource identifier of the right Drawable.
1948 * @param bottom Resource identifier of the bottom Drawable.
1949 *
1950 * @attr ref android.R.styleable#TextView_drawableLeft
1951 * @attr ref android.R.styleable#TextView_drawableTop
1952 * @attr ref android.R.styleable#TextView_drawableRight
1953 * @attr ref android.R.styleable#TextView_drawableBottom
1954 */
Daniel Sandler820ba322012-03-23 16:36:00 -05001955 @android.view.RemotableViewMethod
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001956 public void setCompoundDrawablesWithIntrinsicBounds(int left, int top, int right, int bottom) {
1957 final Resources resources = getContext().getResources();
1958 setCompoundDrawablesWithIntrinsicBounds(left != 0 ? resources.getDrawable(left) : null,
1959 top != 0 ? resources.getDrawable(top) : null,
1960 right != 0 ? resources.getDrawable(right) : null,
1961 bottom != 0 ? resources.getDrawable(bottom) : null);
1962 }
1963
1964 /**
1965 * Sets the Drawables (if any) to appear to the left of, above,
1966 * to the right of, and below the text. Use null if you do not
1967 * want a Drawable there. The Drawables' bounds will be set to
1968 * their intrinsic bounds.
1969 *
1970 * @attr ref android.R.styleable#TextView_drawableLeft
1971 * @attr ref android.R.styleable#TextView_drawableTop
1972 * @attr ref android.R.styleable#TextView_drawableRight
1973 * @attr ref android.R.styleable#TextView_drawableBottom
1974 */
1975 public void setCompoundDrawablesWithIntrinsicBounds(Drawable left, Drawable top,
1976 Drawable right, Drawable bottom) {
1977
1978 if (left != null) {
1979 left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
1980 }
1981 if (right != null) {
1982 right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
1983 }
1984 if (top != null) {
1985 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
1986 }
1987 if (bottom != null) {
1988 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
1989 }
1990 setCompoundDrawables(left, top, right, bottom);
1991 }
1992
1993 /**
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001994 * Sets the Drawables (if any) to appear to the start of, above,
1995 * to the end of, and below the text. Use null if you do not
1996 * want a Drawable there. The Drawables must already have had
1997 * {@link Drawable#setBounds} called.
1998 *
1999 * @attr ref android.R.styleable#TextView_drawableStart
2000 * @attr ref android.R.styleable#TextView_drawableTop
2001 * @attr ref android.R.styleable#TextView_drawableEnd
2002 * @attr ref android.R.styleable#TextView_drawableBottom
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002003 */
2004 public void setCompoundDrawablesRelative(Drawable start, Drawable top,
2005 Drawable end, Drawable bottom) {
2006 Drawables dr = mDrawables;
2007
2008 final boolean drawables = start != null || top != null
2009 || end != null || bottom != null;
2010
2011 if (!drawables) {
2012 // Clearing drawables... can we free the data structure?
2013 if (dr != null) {
2014 if (dr.mDrawablePadding == 0) {
2015 mDrawables = null;
2016 } else {
2017 // We need to retain the last set padding, so just clear
2018 // out all of the fields in the existing structure.
2019 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
2020 dr.mDrawableStart = null;
2021 if (dr.mDrawableTop != null) dr.mDrawableTop.setCallback(null);
2022 dr.mDrawableTop = null;
2023 if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null);
2024 dr.mDrawableEnd = null;
2025 if (dr.mDrawableBottom != null) dr.mDrawableBottom.setCallback(null);
2026 dr.mDrawableBottom = null;
2027 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2028 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2029 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2030 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2031 }
2032 }
2033 } else {
2034 if (dr == null) {
2035 mDrawables = dr = new Drawables();
2036 }
2037
2038 if (dr.mDrawableStart != start && dr.mDrawableStart != null) {
2039 dr.mDrawableStart.setCallback(null);
2040 }
2041 dr.mDrawableStart = start;
2042
2043 if (dr.mDrawableTop != top && dr.mDrawableTop != null) {
2044 dr.mDrawableTop.setCallback(null);
2045 }
2046 dr.mDrawableTop = top;
2047
2048 if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) {
2049 dr.mDrawableEnd.setCallback(null);
2050 }
2051 dr.mDrawableEnd = end;
2052
2053 if (dr.mDrawableBottom != bottom && dr.mDrawableBottom != null) {
2054 dr.mDrawableBottom.setCallback(null);
2055 }
2056 dr.mDrawableBottom = bottom;
2057
2058 final Rect compoundRect = dr.mCompoundRect;
2059 int[] state;
2060
2061 state = getDrawableState();
2062
2063 if (start != null) {
2064 start.setState(state);
2065 start.copyBounds(compoundRect);
2066 start.setCallback(this);
2067 dr.mDrawableSizeStart = compoundRect.width();
2068 dr.mDrawableHeightStart = compoundRect.height();
2069 } else {
2070 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2071 }
2072
2073 if (end != null) {
2074 end.setState(state);
2075 end.copyBounds(compoundRect);
2076 end.setCallback(this);
2077 dr.mDrawableSizeEnd = compoundRect.width();
2078 dr.mDrawableHeightEnd = compoundRect.height();
2079 } else {
2080 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2081 }
2082
2083 if (top != null) {
2084 top.setState(state);
2085 top.copyBounds(compoundRect);
2086 top.setCallback(this);
2087 dr.mDrawableSizeTop = compoundRect.height();
2088 dr.mDrawableWidthTop = compoundRect.width();
2089 } else {
2090 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2091 }
2092
2093 if (bottom != null) {
2094 bottom.setState(state);
2095 bottom.copyBounds(compoundRect);
2096 bottom.setCallback(this);
2097 dr.mDrawableSizeBottom = compoundRect.height();
2098 dr.mDrawableWidthBottom = compoundRect.width();
2099 } else {
2100 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2101 }
2102 }
2103
2104 resolveDrawables();
2105 invalidate();
2106 requestLayout();
2107 }
2108
2109 /**
2110 * Sets the Drawables (if any) to appear to the start of, above,
2111 * to the end of, and below the text. Use 0 if you do not
2112 * want a Drawable there. The Drawables' bounds will be set to
2113 * their intrinsic bounds.
2114 *
2115 * @param start Resource identifier of the start Drawable.
2116 * @param top Resource identifier of the top Drawable.
2117 * @param end Resource identifier of the end Drawable.
2118 * @param bottom Resource identifier of the bottom Drawable.
2119 *
2120 * @attr ref android.R.styleable#TextView_drawableStart
2121 * @attr ref android.R.styleable#TextView_drawableTop
2122 * @attr ref android.R.styleable#TextView_drawableEnd
2123 * @attr ref android.R.styleable#TextView_drawableBottom
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002124 */
Daniel Sandler820ba322012-03-23 16:36:00 -05002125 @android.view.RemotableViewMethod
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002126 public void setCompoundDrawablesRelativeWithIntrinsicBounds(int start, int top, int end,
2127 int bottom) {
2128 resetResolvedDrawables();
2129 final Resources resources = getContext().getResources();
2130 setCompoundDrawablesRelativeWithIntrinsicBounds(
2131 start != 0 ? resources.getDrawable(start) : null,
2132 top != 0 ? resources.getDrawable(top) : null,
2133 end != 0 ? resources.getDrawable(end) : null,
2134 bottom != 0 ? resources.getDrawable(bottom) : null);
2135 }
2136
2137 /**
2138 * Sets the Drawables (if any) to appear to the start of, above,
2139 * to the end of, and below the text. Use null if you do not
2140 * want a Drawable there. The Drawables' bounds will be set to
2141 * their intrinsic bounds.
2142 *
2143 * @attr ref android.R.styleable#TextView_drawableStart
2144 * @attr ref android.R.styleable#TextView_drawableTop
2145 * @attr ref android.R.styleable#TextView_drawableEnd
2146 * @attr ref android.R.styleable#TextView_drawableBottom
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002147 */
2148 public void setCompoundDrawablesRelativeWithIntrinsicBounds(Drawable start, Drawable top,
2149 Drawable end, Drawable bottom) {
2150
2151 resetResolvedDrawables();
2152 if (start != null) {
2153 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
2154 }
2155 if (end != null) {
2156 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
2157 }
2158 if (top != null) {
2159 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
2160 }
2161 if (bottom != null) {
2162 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
2163 }
2164 setCompoundDrawablesRelative(start, top, end, bottom);
2165 }
2166
2167 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002168 * Returns drawables for the left, top, right, and bottom borders.
Gilles Debunnef03acef2012-04-30 19:26:19 -07002169 *
2170 * @attr ref android.R.styleable#TextView_drawableLeft
2171 * @attr ref android.R.styleable#TextView_drawableTop
2172 * @attr ref android.R.styleable#TextView_drawableRight
2173 * @attr ref android.R.styleable#TextView_drawableBottom
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002174 */
2175 public Drawable[] getCompoundDrawables() {
2176 final Drawables dr = mDrawables;
2177 if (dr != null) {
2178 return new Drawable[] {
2179 dr.mDrawableLeft, dr.mDrawableTop, dr.mDrawableRight, dr.mDrawableBottom
2180 };
2181 } else {
2182 return new Drawable[] { null, null, null, null };
2183 }
2184 }
2185
2186 /**
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002187 * Returns drawables for the start, top, end, and bottom borders.
Gilles Debunnef03acef2012-04-30 19:26:19 -07002188 *
2189 * @attr ref android.R.styleable#TextView_drawableStart
2190 * @attr ref android.R.styleable#TextView_drawableTop
2191 * @attr ref android.R.styleable#TextView_drawableEnd
2192 * @attr ref android.R.styleable#TextView_drawableBottom
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002193 */
2194 public Drawable[] getCompoundDrawablesRelative() {
2195 final Drawables dr = mDrawables;
2196 if (dr != null) {
2197 return new Drawable[] {
2198 dr.mDrawableStart, dr.mDrawableTop, dr.mDrawableEnd, dr.mDrawableBottom
2199 };
2200 } else {
2201 return new Drawable[] { null, null, null, null };
2202 }
2203 }
2204
2205 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002206 * Sets the size of the padding between the compound drawables and
2207 * the text.
2208 *
2209 * @attr ref android.R.styleable#TextView_drawablePadding
2210 */
Daniel Sandler820ba322012-03-23 16:36:00 -05002211 @android.view.RemotableViewMethod
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002212 public void setCompoundDrawablePadding(int pad) {
2213 Drawables dr = mDrawables;
2214 if (pad == 0) {
2215 if (dr != null) {
2216 dr.mDrawablePadding = pad;
2217 }
2218 } else {
2219 if (dr == null) {
2220 mDrawables = dr = new Drawables();
2221 }
2222 dr.mDrawablePadding = pad;
2223 }
2224
2225 invalidate();
2226 requestLayout();
2227 }
2228
2229 /**
2230 * Returns the padding between the compound drawables and the text.
Gilles Debunnef03acef2012-04-30 19:26:19 -07002231 *
2232 * @attr ref android.R.styleable#TextView_drawablePadding
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002233 */
2234 public int getCompoundDrawablePadding() {
2235 final Drawables dr = mDrawables;
2236 return dr != null ? dr.mDrawablePadding : 0;
2237 }
2238
2239 @Override
2240 public void setPadding(int left, int top, int right, int bottom) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07002241 if (left != mPaddingLeft ||
2242 right != mPaddingRight ||
2243 top != mPaddingTop ||
2244 bottom != mPaddingBottom) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002245 nullLayouts();
2246 }
2247
2248 // the super call will requestLayout()
2249 super.setPadding(left, top, right, bottom);
2250 invalidate();
2251 }
2252
Fabrice Di Megliobf923eb2012-03-07 16:20:22 -08002253 @Override
2254 public void setPaddingRelative(int start, int top, int end, int bottom) {
2255 if (start != getPaddingStart() ||
2256 end != getPaddingEnd() ||
2257 top != mPaddingTop ||
2258 bottom != mPaddingBottom) {
2259 nullLayouts();
2260 }
2261
2262 // the super call will requestLayout()
2263 super.setPaddingRelative(start, top, end, bottom);
2264 invalidate();
2265 }
2266
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002267 /**
2268 * Gets the autolink mask of the text. See {@link
2269 * android.text.util.Linkify#ALL Linkify.ALL} and peers for
2270 * possible values.
2271 *
2272 * @attr ref android.R.styleable#TextView_autoLink
2273 */
2274 public final int getAutoLinkMask() {
2275 return mAutoLinkMask;
2276 }
2277
2278 /**
2279 * Sets the text color, size, style, hint color, and highlight color
2280 * from the specified TextAppearance resource.
2281 */
2282 public void setTextAppearance(Context context, int resid) {
2283 TypedArray appearance =
2284 context.obtainStyledAttributes(resid,
2285 com.android.internal.R.styleable.TextAppearance);
2286
2287 int color;
2288 ColorStateList colors;
2289 int ts;
2290
Gilles Debunne2d373a12012-04-20 15:32:19 -07002291 color = appearance.getColor(
2292 com.android.internal.R.styleable.TextAppearance_textColorHighlight, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002293 if (color != 0) {
2294 setHighlightColor(color);
2295 }
2296
2297 colors = appearance.getColorStateList(com.android.internal.R.styleable.
2298 TextAppearance_textColor);
2299 if (colors != null) {
2300 setTextColor(colors);
2301 }
2302
2303 ts = appearance.getDimensionPixelSize(com.android.internal.R.styleable.
2304 TextAppearance_textSize, 0);
2305 if (ts != 0) {
2306 setRawTextSize(ts);
2307 }
2308
2309 colors = appearance.getColorStateList(com.android.internal.R.styleable.
2310 TextAppearance_textColorHint);
2311 if (colors != null) {
2312 setHintTextColor(colors);
2313 }
2314
2315 colors = appearance.getColorStateList(com.android.internal.R.styleable.
2316 TextAppearance_textColorLink);
2317 if (colors != null) {
2318 setLinkTextColor(colors);
2319 }
2320
Raph Leviend570e892012-05-09 11:45:34 -07002321 String familyName;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002322 int typefaceIndex, styleIndex;
2323
Raph Leviend570e892012-05-09 11:45:34 -07002324 familyName = appearance.getString(com.android.internal.R.styleable.
2325 TextAppearance_fontFamily);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002326 typefaceIndex = appearance.getInt(com.android.internal.R.styleable.
2327 TextAppearance_typeface, -1);
2328 styleIndex = appearance.getInt(com.android.internal.R.styleable.
2329 TextAppearance_textStyle, -1);
2330
Raph Leviend570e892012-05-09 11:45:34 -07002331 setTypefaceFromAttrs(familyName, typefaceIndex, styleIndex);
Gilles Debunne21078e42011-08-02 10:22:35 -07002332
Adam Powell7f8f79a2011-07-07 18:35:54 -07002333 if (appearance.getBoolean(com.android.internal.R.styleable.TextAppearance_textAllCaps,
2334 false)) {
2335 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
2336 }
2337
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002338 appearance.recycle();
2339 }
2340
2341 /**
Victoria Leasedf8ef4b2012-08-17 15:34:01 -07002342 * Get the default {@link Locale} of the text in this TextView.
2343 * @return the default {@link Locale} of the text in this TextView.
2344 */
2345 public Locale getTextLocale() {
2346 return mTextPaint.getTextLocale();
2347 }
2348
2349 /**
2350 * Set the default {@link Locale} of the text in this TextView to the given value. This value
2351 * is used to choose appropriate typefaces for ambiguous characters. Typically used for CJK
2352 * locales to disambiguate Hanzi/Kanji/Hanja characters.
2353 *
2354 * @param locale the {@link Locale} for drawing text, must not be null.
2355 *
2356 * @see Paint#setTextLocale
2357 */
2358 public void setTextLocale(Locale locale) {
2359 mTextPaint.setTextLocale(locale);
2360 }
2361
2362 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002363 * @return the size (in pixels) of the default text size in this TextView.
2364 */
Fabrice Di Meglioc54da1c2012-04-27 16:16:35 -07002365 @ViewDebug.ExportedProperty(category = "text")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002366 public float getTextSize() {
2367 return mTextPaint.getTextSize();
2368 }
2369
2370 /**
2371 * Set the default text size to the given value, interpreted as "scaled
2372 * pixel" units. This size is adjusted based on the current density and
2373 * user font size preference.
2374 *
2375 * @param size The scaled pixel size.
2376 *
2377 * @attr ref android.R.styleable#TextView_textSize
2378 */
2379 @android.view.RemotableViewMethod
2380 public void setTextSize(float size) {
2381 setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
2382 }
2383
2384 /**
2385 * Set the default text size to a given unit and value. See {@link
2386 * TypedValue} for the possible dimension units.
2387 *
2388 * @param unit The desired dimension unit.
2389 * @param size The desired size in the given units.
2390 *
2391 * @attr ref android.R.styleable#TextView_textSize
2392 */
2393 public void setTextSize(int unit, float size) {
2394 Context c = getContext();
2395 Resources r;
2396
2397 if (c == null)
2398 r = Resources.getSystem();
2399 else
2400 r = c.getResources();
2401
2402 setRawTextSize(TypedValue.applyDimension(
2403 unit, size, r.getDisplayMetrics()));
2404 }
2405
2406 private void setRawTextSize(float size) {
2407 if (size != mTextPaint.getTextSize()) {
2408 mTextPaint.setTextSize(size);
2409
2410 if (mLayout != null) {
2411 nullLayouts();
2412 requestLayout();
2413 invalidate();
2414 }
2415 }
2416 }
2417
2418 /**
2419 * @return the extent by which text is currently being stretched
2420 * horizontally. This will usually be 1.
2421 */
2422 public float getTextScaleX() {
2423 return mTextPaint.getTextScaleX();
2424 }
2425
2426 /**
2427 * Sets the extent by which text should be stretched horizontally.
2428 *
2429 * @attr ref android.R.styleable#TextView_textScaleX
2430 */
2431 @android.view.RemotableViewMethod
2432 public void setTextScaleX(float size) {
2433 if (size != mTextPaint.getTextScaleX()) {
Romain Guy939151f2009-04-08 14:22:40 -07002434 mUserSetTextScaleX = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002435 mTextPaint.setTextScaleX(size);
2436
2437 if (mLayout != null) {
2438 nullLayouts();
2439 requestLayout();
2440 invalidate();
2441 }
2442 }
2443 }
2444
2445 /**
2446 * Sets the typeface and style in which the text should be displayed.
2447 * Note that not all Typeface families actually have bold and italic
2448 * variants, so you may need to use
2449 * {@link #setTypeface(Typeface, int)} to get the appearance
2450 * that you actually want.
2451 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002452 * @see #getTypeface()
2453 *
Raph Leviend570e892012-05-09 11:45:34 -07002454 * @attr ref android.R.styleable#TextView_fontFamily
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002455 * @attr ref android.R.styleable#TextView_typeface
2456 * @attr ref android.R.styleable#TextView_textStyle
2457 */
2458 public void setTypeface(Typeface tf) {
2459 if (mTextPaint.getTypeface() != tf) {
2460 mTextPaint.setTypeface(tf);
2461
2462 if (mLayout != null) {
2463 nullLayouts();
2464 requestLayout();
2465 invalidate();
2466 }
2467 }
2468 }
2469
2470 /**
2471 * @return the current typeface and style in which the text is being
2472 * displayed.
Gilles Debunnef03acef2012-04-30 19:26:19 -07002473 *
2474 * @see #setTypeface(Typeface)
2475 *
Raph Leviend570e892012-05-09 11:45:34 -07002476 * @attr ref android.R.styleable#TextView_fontFamily
Gilles Debunnef03acef2012-04-30 19:26:19 -07002477 * @attr ref android.R.styleable#TextView_typeface
2478 * @attr ref android.R.styleable#TextView_textStyle
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002479 */
2480 public Typeface getTypeface() {
2481 return mTextPaint.getTypeface();
2482 }
2483
2484 /**
2485 * Sets the text color for all the states (normal, selected,
2486 * focused) to be this color.
2487 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002488 * @see #setTextColor(ColorStateList)
2489 * @see #getTextColors()
2490 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002491 * @attr ref android.R.styleable#TextView_textColor
2492 */
2493 @android.view.RemotableViewMethod
2494 public void setTextColor(int color) {
2495 mTextColor = ColorStateList.valueOf(color);
2496 updateTextColors();
2497 }
2498
2499 /**
2500 * Sets the text color.
2501 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002502 * @see #setTextColor(int)
2503 * @see #getTextColors()
2504 * @see #setHintTextColor(ColorStateList)
2505 * @see #setLinkTextColor(ColorStateList)
2506 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002507 * @attr ref android.R.styleable#TextView_textColor
2508 */
2509 public void setTextColor(ColorStateList colors) {
2510 if (colors == null) {
2511 throw new NullPointerException();
2512 }
2513
2514 mTextColor = colors;
2515 updateTextColors();
2516 }
2517
2518 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002519 * Gets the text colors for the different states (normal, selected, focused) of the TextView.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002520 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002521 * @see #setTextColor(ColorStateList)
2522 * @see #setTextColor(int)
2523 *
2524 * @attr ref android.R.styleable#TextView_textColor
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002525 */
2526 public final ColorStateList getTextColors() {
2527 return mTextColor;
2528 }
2529
2530 /**
2531 * <p>Return the current color selected for normal text.</p>
2532 *
2533 * @return Returns the current text color.
2534 */
2535 public final int getCurrentTextColor() {
2536 return mCurTextColor;
2537 }
2538
2539 /**
2540 * Sets the color used to display the selection highlight.
2541 *
2542 * @attr ref android.R.styleable#TextView_textColorHighlight
2543 */
2544 @android.view.RemotableViewMethod
2545 public void setHighlightColor(int color) {
2546 if (mHighlightColor != color) {
2547 mHighlightColor = color;
2548 invalidate();
2549 }
2550 }
2551
2552 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002553 * @return the color used to display the selection highlight
2554 *
2555 * @see #setHighlightColor(int)
2556 *
2557 * @attr ref android.R.styleable#TextView_textColorHighlight
2558 */
2559 public int getHighlightColor() {
2560 return mHighlightColor;
2561 }
2562
2563 /**
Gilles Debunne3473b2b2012-04-20 16:21:10 -07002564 * Sets whether the soft input method will be made visible when this
2565 * TextView gets focused. The default is true.
2566 * @hide
2567 */
2568 @android.view.RemotableViewMethod
2569 public final void setShowSoftInputOnFocus(boolean show) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07002570 createEditorIfNeeded();
Gilles Debunne3473b2b2012-04-20 16:21:10 -07002571 mEditor.mShowSoftInputOnFocus = show;
2572 }
2573
2574 /**
2575 * Returns whether the soft input method will be made visible when this
2576 * TextView gets focused. The default is true.
2577 * @hide
2578 */
2579 public final boolean getShowSoftInputOnFocus() {
2580 // When there is no Editor, return default true value
2581 return mEditor == null || mEditor.mShowSoftInputOnFocus;
2582 }
2583
2584 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002585 * Gives the text a shadow of the specified radius and color, the specified
2586 * distance from its normal position.
2587 *
2588 * @attr ref android.R.styleable#TextView_shadowColor
2589 * @attr ref android.R.styleable#TextView_shadowDx
2590 * @attr ref android.R.styleable#TextView_shadowDy
2591 * @attr ref android.R.styleable#TextView_shadowRadius
2592 */
2593 public void setShadowLayer(float radius, float dx, float dy, int color) {
2594 mTextPaint.setShadowLayer(radius, dx, dy, color);
2595
2596 mShadowRadius = radius;
2597 mShadowDx = dx;
2598 mShadowDy = dy;
2599
Gilles Debunne33b7de852012-03-12 11:57:48 -07002600 // Will change text clip region
Gilles Debunne2d373a12012-04-20 15:32:19 -07002601 if (mEditor != null) mEditor.invalidateTextDisplayList();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002602 invalidate();
2603 }
2604
2605 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002606 * Gets the radius of the shadow layer.
2607 *
2608 * @return the radius of the shadow layer. If 0, the shadow layer is not visible
2609 *
2610 * @see #setShadowLayer(float, float, float, int)
2611 *
2612 * @attr ref android.R.styleable#TextView_shadowRadius
2613 */
2614 public float getShadowRadius() {
2615 return mShadowRadius;
2616 }
2617
2618 /**
2619 * @return the horizontal offset of the shadow layer
2620 *
2621 * @see #setShadowLayer(float, float, float, int)
2622 *
2623 * @attr ref android.R.styleable#TextView_shadowDx
2624 */
2625 public float getShadowDx() {
2626 return mShadowDx;
2627 }
2628
2629 /**
2630 * @return the vertical offset of the shadow layer
2631 *
2632 * @see #setShadowLayer(float, float, float, int)
2633 *
2634 * @attr ref android.R.styleable#TextView_shadowDy
2635 */
2636 public float getShadowDy() {
2637 return mShadowDy;
2638 }
2639
2640 /**
2641 * @return the color of the shadow layer
2642 *
2643 * @see #setShadowLayer(float, float, float, int)
2644 *
2645 * @attr ref android.R.styleable#TextView_shadowColor
2646 */
2647 public int getShadowColor() {
2648 return mTextPaint.shadowColor;
2649 }
2650
2651 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002652 * @return the base paint used for the text. Please use this only to
2653 * consult the Paint's properties and not to change them.
2654 */
2655 public TextPaint getPaint() {
2656 return mTextPaint;
2657 }
2658
2659 /**
2660 * Sets the autolink mask of the text. See {@link
2661 * android.text.util.Linkify#ALL Linkify.ALL} and peers for
2662 * possible values.
2663 *
2664 * @attr ref android.R.styleable#TextView_autoLink
2665 */
2666 @android.view.RemotableViewMethod
2667 public final void setAutoLinkMask(int mask) {
2668 mAutoLinkMask = mask;
2669 }
2670
2671 /**
2672 * Sets whether the movement method will automatically be set to
2673 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
2674 * set to nonzero and links are detected in {@link #setText}.
2675 * The default is true.
2676 *
2677 * @attr ref android.R.styleable#TextView_linksClickable
2678 */
2679 @android.view.RemotableViewMethod
2680 public final void setLinksClickable(boolean whether) {
2681 mLinksClickable = whether;
2682 }
2683
2684 /**
2685 * Returns whether the movement method will automatically be set to
2686 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
2687 * set to nonzero and links are detected in {@link #setText}.
2688 * The default is true.
2689 *
2690 * @attr ref android.R.styleable#TextView_linksClickable
2691 */
2692 public final boolean getLinksClickable() {
2693 return mLinksClickable;
2694 }
2695
2696 /**
2697 * Returns the list of URLSpans attached to the text
2698 * (by {@link Linkify} or otherwise) if any. You can call
2699 * {@link URLSpan#getURL} on them to find where they link to
2700 * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
2701 * to find the region of the text they are attached to.
2702 */
2703 public URLSpan[] getUrls() {
2704 if (mText instanceof Spanned) {
2705 return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
2706 } else {
2707 return new URLSpan[0];
2708 }
2709 }
2710
2711 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002712 * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this
2713 * TextView.
2714 *
2715 * @see #setHintTextColor(ColorStateList)
2716 * @see #getHintTextColors()
2717 * @see #setTextColor(int)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002718 *
2719 * @attr ref android.R.styleable#TextView_textColorHint
2720 */
2721 @android.view.RemotableViewMethod
2722 public final void setHintTextColor(int color) {
2723 mHintTextColor = ColorStateList.valueOf(color);
2724 updateTextColors();
2725 }
2726
2727 /**
2728 * Sets the color of the hint text.
2729 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002730 * @see #getHintTextColors()
2731 * @see #setHintTextColor(int)
2732 * @see #setTextColor(ColorStateList)
2733 * @see #setLinkTextColor(ColorStateList)
2734 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002735 * @attr ref android.R.styleable#TextView_textColorHint
2736 */
2737 public final void setHintTextColor(ColorStateList colors) {
2738 mHintTextColor = colors;
2739 updateTextColors();
2740 }
2741
2742 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002743 * @return the color of the hint text, for the different states of this TextView.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002744 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002745 * @see #setHintTextColor(ColorStateList)
2746 * @see #setHintTextColor(int)
2747 * @see #setTextColor(ColorStateList)
2748 * @see #setLinkTextColor(ColorStateList)
2749 *
2750 * @attr ref android.R.styleable#TextView_textColorHint
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002751 */
2752 public final ColorStateList getHintTextColors() {
2753 return mHintTextColor;
2754 }
2755
2756 /**
2757 * <p>Return the current color selected to paint the hint text.</p>
2758 *
2759 * @return Returns the current hint text color.
2760 */
2761 public final int getCurrentHintTextColor() {
2762 return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
2763 }
2764
2765 /**
2766 * Sets the color of links in the text.
2767 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002768 * @see #setLinkTextColor(ColorStateList)
2769 * @see #getLinkTextColors()
2770 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002771 * @attr ref android.R.styleable#TextView_textColorLink
2772 */
2773 @android.view.RemotableViewMethod
2774 public final void setLinkTextColor(int color) {
2775 mLinkTextColor = ColorStateList.valueOf(color);
2776 updateTextColors();
2777 }
2778
2779 /**
2780 * Sets the color of links in the text.
2781 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002782 * @see #setLinkTextColor(int)
2783 * @see #getLinkTextColors()
2784 * @see #setTextColor(ColorStateList)
2785 * @see #setHintTextColor(ColorStateList)
2786 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002787 * @attr ref android.R.styleable#TextView_textColorLink
2788 */
2789 public final void setLinkTextColor(ColorStateList colors) {
2790 mLinkTextColor = colors;
2791 updateTextColors();
2792 }
2793
2794 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002795 * @return the list of colors used to paint the links in the text, for the different states of
2796 * this TextView
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002797 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002798 * @see #setLinkTextColor(ColorStateList)
2799 * @see #setLinkTextColor(int)
2800 *
2801 * @attr ref android.R.styleable#TextView_textColorLink
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002802 */
2803 public final ColorStateList getLinkTextColors() {
2804 return mLinkTextColor;
2805 }
2806
2807 /**
2808 * Sets the horizontal alignment of the text and the
2809 * vertical gravity that will be used when there is extra space
2810 * in the TextView beyond what is required for the text itself.
2811 *
2812 * @see android.view.Gravity
2813 * @attr ref android.R.styleable#TextView_gravity
2814 */
2815 public void setGravity(int gravity) {
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07002816 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
Fabrice Di Meglio9e3b0022011-06-06 16:30:29 -07002817 gravity |= Gravity.START;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002818 }
2819 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
2820 gravity |= Gravity.TOP;
2821 }
2822
2823 boolean newLayout = false;
2824
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07002825 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) !=
2826 (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002827 newLayout = true;
2828 }
2829
2830 if (gravity != mGravity) {
2831 invalidate();
Fabrice Di Meglio9f513842011-10-12 11:43:27 -07002832 mLayoutAlignment = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002833 }
2834
2835 mGravity = gravity;
2836
2837 if (mLayout != null && newLayout) {
2838 // XXX this is heavy-handed because no actual content changes.
2839 int want = mLayout.getWidth();
2840 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
2841
2842 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
2843 mRight - mLeft - getCompoundPaddingLeft() -
2844 getCompoundPaddingRight(), true);
2845 }
2846 }
2847
2848 /**
2849 * Returns the horizontal and vertical alignment of this TextView.
2850 *
2851 * @see android.view.Gravity
2852 * @attr ref android.R.styleable#TextView_gravity
2853 */
2854 public int getGravity() {
2855 return mGravity;
2856 }
2857
2858 /**
2859 * @return the flags on the Paint being used to display the text.
2860 * @see Paint#getFlags
2861 */
2862 public int getPaintFlags() {
2863 return mTextPaint.getFlags();
2864 }
2865
2866 /**
2867 * Sets flags on the Paint being used to display the text and
2868 * reflows the text if they are different from the old flags.
2869 * @see Paint#setFlags
2870 */
2871 @android.view.RemotableViewMethod
2872 public void setPaintFlags(int flags) {
2873 if (mTextPaint.getFlags() != flags) {
2874 mTextPaint.setFlags(flags);
2875
2876 if (mLayout != null) {
2877 nullLayouts();
2878 requestLayout();
2879 invalidate();
2880 }
2881 }
2882 }
2883
2884 /**
2885 * Sets whether the text should be allowed to be wider than the
2886 * View is. If false, it will be wrapped to the width of the View.
2887 *
2888 * @attr ref android.R.styleable#TextView_scrollHorizontally
2889 */
2890 public void setHorizontallyScrolling(boolean whether) {
Gilles Debunne22378292011-08-12 10:38:52 -07002891 if (mHorizontallyScrolling != whether) {
2892 mHorizontallyScrolling = whether;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002893
Gilles Debunne22378292011-08-12 10:38:52 -07002894 if (mLayout != null) {
2895 nullLayouts();
2896 requestLayout();
2897 invalidate();
2898 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002899 }
2900 }
2901
2902 /**
Gilles Debunnef2a02012011-10-27 11:10:14 -07002903 * Returns whether the text is allowed to be wider than the View is.
2904 * If false, the text will be wrapped to the width of the View.
2905 *
2906 * @attr ref android.R.styleable#TextView_scrollHorizontally
2907 * @hide
2908 */
2909 public boolean getHorizontallyScrolling() {
2910 return mHorizontallyScrolling;
2911 }
2912
2913 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08002914 * Makes the TextView at least this many lines tall.
2915 *
2916 * Setting this value overrides any other (minimum) height setting. A single line TextView will
2917 * set this value to 1.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002918 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002919 * @see #getMinLines()
2920 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002921 * @attr ref android.R.styleable#TextView_minLines
2922 */
2923 @android.view.RemotableViewMethod
2924 public void setMinLines(int minlines) {
2925 mMinimum = minlines;
2926 mMinMode = LINES;
2927
2928 requestLayout();
2929 invalidate();
2930 }
2931
2932 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002933 * @return the minimum number of lines displayed in this TextView, or -1 if the minimum
2934 * height was set in pixels instead using {@link #setMinHeight(int) or #setHeight(int)}.
2935 *
2936 * @see #setMinLines(int)
2937 *
2938 * @attr ref android.R.styleable#TextView_minLines
2939 */
2940 public int getMinLines() {
2941 return mMinMode == LINES ? mMinimum : -1;
2942 }
2943
2944 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08002945 * Makes the TextView at least this many pixels tall.
2946 *
2947 * Setting this value overrides any other (minimum) number of lines setting.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002948 *
2949 * @attr ref android.R.styleable#TextView_minHeight
2950 */
2951 @android.view.RemotableViewMethod
2952 public void setMinHeight(int minHeight) {
2953 mMinimum = minHeight;
2954 mMinMode = PIXELS;
2955
2956 requestLayout();
2957 invalidate();
2958 }
2959
2960 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002961 * @return the minimum height of this TextView expressed in pixels, or -1 if the minimum
2962 * height was set in number of lines instead using {@link #setMinLines(int) or #setLines(int)}.
2963 *
2964 * @see #setMinHeight(int)
2965 *
2966 * @attr ref android.R.styleable#TextView_minHeight
2967 */
2968 public int getMinHeight() {
2969 return mMinMode == PIXELS ? mMinimum : -1;
2970 }
2971
2972 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08002973 * Makes the TextView at most this many lines tall.
2974 *
2975 * Setting this value overrides any other (maximum) height setting.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002976 *
2977 * @attr ref android.R.styleable#TextView_maxLines
2978 */
2979 @android.view.RemotableViewMethod
2980 public void setMaxLines(int maxlines) {
2981 mMaximum = maxlines;
2982 mMaxMode = LINES;
2983
2984 requestLayout();
2985 invalidate();
2986 }
2987
2988 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002989 * @return the maximum number of lines displayed in this TextView, or -1 if the maximum
2990 * height was set in pixels instead using {@link #setMaxHeight(int) or #setHeight(int)}.
2991 *
2992 * @see #setMaxLines(int)
2993 *
2994 * @attr ref android.R.styleable#TextView_maxLines
2995 */
2996 public int getMaxLines() {
2997 return mMaxMode == LINES ? mMaximum : -1;
2998 }
2999
3000 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003001 * Makes the TextView at most this many pixels tall. This option is mutually exclusive with the
3002 * {@link #setMaxLines(int)} method.
3003 *
3004 * Setting this value overrides any other (maximum) number of lines setting.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003005 *
3006 * @attr ref android.R.styleable#TextView_maxHeight
3007 */
3008 @android.view.RemotableViewMethod
3009 public void setMaxHeight(int maxHeight) {
3010 mMaximum = maxHeight;
3011 mMaxMode = PIXELS;
3012
3013 requestLayout();
3014 invalidate();
3015 }
3016
3017 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003018 * @return the maximum height of this TextView expressed in pixels, or -1 if the maximum
3019 * height was set in number of lines instead using {@link #setMaxLines(int) or #setLines(int)}.
3020 *
3021 * @see #setMaxHeight(int)
3022 *
3023 * @attr ref android.R.styleable#TextView_maxHeight
3024 */
3025 public int getMaxHeight() {
3026 return mMaxMode == PIXELS ? mMaximum : -1;
3027 }
3028
3029 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003030 * Makes the TextView exactly this many lines tall.
3031 *
3032 * Note that setting this value overrides any other (minimum / maximum) number of lines or
3033 * height setting. A single line TextView will set this value to 1.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003034 *
3035 * @attr ref android.R.styleable#TextView_lines
3036 */
3037 @android.view.RemotableViewMethod
3038 public void setLines(int lines) {
3039 mMaximum = mMinimum = lines;
3040 mMaxMode = mMinMode = LINES;
3041
3042 requestLayout();
3043 invalidate();
3044 }
3045
3046 /**
3047 * Makes the TextView exactly this many pixels tall.
3048 * You could do the same thing by specifying this number in the
3049 * LayoutParams.
3050 *
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003051 * Note that setting this value overrides any other (minimum / maximum) number of lines or
3052 * height setting.
3053 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003054 * @attr ref android.R.styleable#TextView_height
3055 */
3056 @android.view.RemotableViewMethod
3057 public void setHeight(int pixels) {
3058 mMaximum = mMinimum = pixels;
3059 mMaxMode = mMinMode = PIXELS;
3060
3061 requestLayout();
3062 invalidate();
3063 }
3064
3065 /**
3066 * Makes the TextView at least this many ems wide
3067 *
3068 * @attr ref android.R.styleable#TextView_minEms
3069 */
3070 @android.view.RemotableViewMethod
3071 public void setMinEms(int minems) {
3072 mMinWidth = minems;
3073 mMinWidthMode = EMS;
3074
3075 requestLayout();
3076 invalidate();
3077 }
3078
3079 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003080 * @return the minimum width of the TextView, expressed in ems or -1 if the minimum width
3081 * was set in pixels instead (using {@link #setMinWidth(int)} or {@link #setWidth(int)}).
3082 *
3083 * @see #setMinEms(int)
3084 * @see #setEms(int)
3085 *
3086 * @attr ref android.R.styleable#TextView_minEms
3087 */
3088 public int getMinEms() {
3089 return mMinWidthMode == EMS ? mMinWidth : -1;
3090 }
3091
3092 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003093 * Makes the TextView at least this many pixels wide
3094 *
3095 * @attr ref android.R.styleable#TextView_minWidth
3096 */
3097 @android.view.RemotableViewMethod
3098 public void setMinWidth(int minpixels) {
3099 mMinWidth = minpixels;
3100 mMinWidthMode = PIXELS;
3101
3102 requestLayout();
3103 invalidate();
3104 }
3105
3106 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003107 * @return the minimum width of the TextView, in pixels or -1 if the minimum width
3108 * was set in ems instead (using {@link #setMinEms(int)} or {@link #setEms(int)}).
3109 *
3110 * @see #setMinWidth(int)
3111 * @see #setWidth(int)
3112 *
3113 * @attr ref android.R.styleable#TextView_minWidth
3114 */
3115 public int getMinWidth() {
3116 return mMinWidthMode == PIXELS ? mMinWidth : -1;
3117 }
3118
3119 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003120 * Makes the TextView at most this many ems wide
3121 *
3122 * @attr ref android.R.styleable#TextView_maxEms
3123 */
3124 @android.view.RemotableViewMethod
3125 public void setMaxEms(int maxems) {
3126 mMaxWidth = maxems;
3127 mMaxWidthMode = EMS;
3128
3129 requestLayout();
3130 invalidate();
3131 }
3132
3133 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003134 * @return the maximum width of the TextView, expressed in ems or -1 if the maximum width
3135 * was set in pixels instead (using {@link #setMaxWidth(int)} or {@link #setWidth(int)}).
3136 *
3137 * @see #setMaxEms(int)
3138 * @see #setEms(int)
3139 *
3140 * @attr ref android.R.styleable#TextView_maxEms
3141 */
3142 public int getMaxEms() {
3143 return mMaxWidthMode == EMS ? mMaxWidth : -1;
3144 }
3145
3146 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003147 * Makes the TextView at most this many pixels wide
3148 *
3149 * @attr ref android.R.styleable#TextView_maxWidth
3150 */
3151 @android.view.RemotableViewMethod
3152 public void setMaxWidth(int maxpixels) {
3153 mMaxWidth = maxpixels;
3154 mMaxWidthMode = PIXELS;
3155
3156 requestLayout();
3157 invalidate();
3158 }
3159
3160 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003161 * @return the maximum width of the TextView, in pixels or -1 if the maximum width
3162 * was set in ems instead (using {@link #setMaxEms(int)} or {@link #setEms(int)}).
3163 *
3164 * @see #setMaxWidth(int)
3165 * @see #setWidth(int)
3166 *
3167 * @attr ref android.R.styleable#TextView_maxWidth
3168 */
3169 public int getMaxWidth() {
3170 return mMaxWidthMode == PIXELS ? mMaxWidth : -1;
3171 }
3172
3173 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003174 * Makes the TextView exactly this many ems wide
3175 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003176 * @see #setMaxEms(int)
3177 * @see #setMinEms(int)
3178 * @see #getMinEms()
3179 * @see #getMaxEms()
3180 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003181 * @attr ref android.R.styleable#TextView_ems
3182 */
3183 @android.view.RemotableViewMethod
3184 public void setEms(int ems) {
3185 mMaxWidth = mMinWidth = ems;
3186 mMaxWidthMode = mMinWidthMode = EMS;
3187
3188 requestLayout();
3189 invalidate();
3190 }
3191
3192 /**
3193 * Makes the TextView exactly this many pixels wide.
3194 * You could do the same thing by specifying this number in the
3195 * LayoutParams.
3196 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003197 * @see #setMaxWidth(int)
3198 * @see #setMinWidth(int)
3199 * @see #getMinWidth()
3200 * @see #getMaxWidth()
3201 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003202 * @attr ref android.R.styleable#TextView_width
3203 */
3204 @android.view.RemotableViewMethod
3205 public void setWidth(int pixels) {
3206 mMaxWidth = mMinWidth = pixels;
3207 mMaxWidthMode = mMinWidthMode = PIXELS;
3208
3209 requestLayout();
3210 invalidate();
3211 }
3212
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003213 /**
3214 * Sets line spacing for this TextView. Each line will have its height
3215 * multiplied by <code>mult</code> and have <code>add</code> added to it.
3216 *
3217 * @attr ref android.R.styleable#TextView_lineSpacingExtra
3218 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
3219 */
3220 public void setLineSpacing(float add, float mult) {
Gilles Debunne22378292011-08-12 10:38:52 -07003221 if (mSpacingAdd != add || mSpacingMult != mult) {
3222 mSpacingAdd = add;
3223 mSpacingMult = mult;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003224
Gilles Debunne22378292011-08-12 10:38:52 -07003225 if (mLayout != null) {
3226 nullLayouts();
3227 requestLayout();
3228 invalidate();
3229 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003230 }
3231 }
3232
3233 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003234 * Gets the line spacing multiplier
3235 *
3236 * @return the value by which each line's height is multiplied to get its actual height.
3237 *
3238 * @see #setLineSpacing(float, float)
3239 * @see #getLineSpacingExtra()
3240 *
3241 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
3242 */
3243 public float getLineSpacingMultiplier() {
3244 return mSpacingMult;
3245 }
3246
3247 /**
3248 * Gets the line spacing extra space
3249 *
3250 * @return the extra space that is added to the height of each lines of this TextView.
3251 *
3252 * @see #setLineSpacing(float, float)
3253 * @see #getLineSpacingMultiplier()
3254 *
3255 * @attr ref android.R.styleable#TextView_lineSpacingExtra
3256 */
3257 public float getLineSpacingExtra() {
3258 return mSpacingAdd;
3259 }
3260
3261 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003262 * Convenience method: Append the specified text to the TextView's
3263 * display buffer, upgrading it to BufferType.EDITABLE if it was
3264 * not already editable.
3265 */
3266 public final void append(CharSequence text) {
3267 append(text, 0, text.length());
3268 }
3269
3270 /**
3271 * Convenience method: Append the specified text slice to the TextView's
3272 * display buffer, upgrading it to BufferType.EDITABLE if it was
3273 * not already editable.
3274 */
3275 public void append(CharSequence text, int start, int end) {
3276 if (!(mText instanceof Editable)) {
3277 setText(mText, BufferType.EDITABLE);
3278 }
3279
3280 ((Editable) mText).append(text, start, end);
3281 }
3282
3283 private void updateTextColors() {
3284 boolean inval = false;
3285 int color = mTextColor.getColorForState(getDrawableState(), 0);
3286 if (color != mCurTextColor) {
3287 mCurTextColor = color;
3288 inval = true;
3289 }
3290 if (mLinkTextColor != null) {
3291 color = mLinkTextColor.getColorForState(getDrawableState(), 0);
3292 if (color != mTextPaint.linkColor) {
3293 mTextPaint.linkColor = color;
3294 inval = true;
3295 }
3296 }
3297 if (mHintTextColor != null) {
3298 color = mHintTextColor.getColorForState(getDrawableState(), 0);
3299 if (color != mCurHintTextColor && mText.length() == 0) {
3300 mCurHintTextColor = color;
3301 inval = true;
3302 }
3303 }
3304 if (inval) {
Gilles Debunne33b7de852012-03-12 11:57:48 -07003305 // Text needs to be redrawn with the new color
Gilles Debunne2d373a12012-04-20 15:32:19 -07003306 if (mEditor != null) mEditor.invalidateTextDisplayList();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003307 invalidate();
3308 }
3309 }
3310
3311 @Override
3312 protected void drawableStateChanged() {
3313 super.drawableStateChanged();
3314 if (mTextColor != null && mTextColor.isStateful()
3315 || (mHintTextColor != null && mHintTextColor.isStateful())
3316 || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
3317 updateTextColors();
3318 }
3319
3320 final Drawables dr = mDrawables;
3321 if (dr != null) {
3322 int[] state = getDrawableState();
3323 if (dr.mDrawableTop != null && dr.mDrawableTop.isStateful()) {
3324 dr.mDrawableTop.setState(state);
3325 }
3326 if (dr.mDrawableBottom != null && dr.mDrawableBottom.isStateful()) {
3327 dr.mDrawableBottom.setState(state);
3328 }
3329 if (dr.mDrawableLeft != null && dr.mDrawableLeft.isStateful()) {
3330 dr.mDrawableLeft.setState(state);
3331 }
3332 if (dr.mDrawableRight != null && dr.mDrawableRight.isStateful()) {
3333 dr.mDrawableRight.setState(state);
3334 }
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07003335 if (dr.mDrawableStart != null && dr.mDrawableStart.isStateful()) {
3336 dr.mDrawableStart.setState(state);
3337 }
3338 if (dr.mDrawableEnd != null && dr.mDrawableEnd.isStateful()) {
3339 dr.mDrawableEnd.setState(state);
3340 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003341 }
3342 }
3343
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003344 @Override
3345 public Parcelable onSaveInstanceState() {
3346 Parcelable superState = super.onSaveInstanceState();
3347
3348 // Save state if we are forced to
3349 boolean save = mFreezesText;
3350 int start = 0;
3351 int end = 0;
3352
3353 if (mText != null) {
Gilles Debunne05336272010-07-09 20:13:45 -07003354 start = getSelectionStart();
3355 end = getSelectionEnd();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003356 if (start >= 0 || end >= 0) {
3357 // Or save state if there is a selection
3358 save = true;
3359 }
3360 }
3361
3362 if (save) {
3363 SavedState ss = new SavedState(superState);
3364 // XXX Should also save the current scroll position!
3365 ss.selStart = start;
3366 ss.selEnd = end;
3367
3368 if (mText instanceof Spanned) {
3369 /*
3370 * Calling setText() strips off any ChangeWatchers;
3371 * strip them now to avoid leaking references.
3372 * But do it to a copy so that if there are any
3373 * further changes to the text of this view, it
3374 * won't get into an inconsistent state.
3375 */
3376
3377 Spannable sp = new SpannableString(mText);
3378
Gilles Debunne176cd0d2011-09-29 16:37:27 -07003379 for (ChangeWatcher cw : sp.getSpans(0, sp.length(), ChangeWatcher.class)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003380 sp.removeSpan(cw);
3381 }
3382
Gilles Debunne60e21862012-01-30 15:04:14 -08003383 if (mEditor != null) {
3384 removeMisspelledSpans(sp);
Gilles Debunne2d373a12012-04-20 15:32:19 -07003385 sp.removeSpan(mEditor.mSuggestionRangeSpan);
Gilles Debunne60e21862012-01-30 15:04:14 -08003386 }
Gilles Debunneaa67eef2011-06-01 18:03:37 -07003387
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003388 ss.text = sp;
3389 } else {
3390 ss.text = mText.toString();
3391 }
3392
3393 if (isFocused() && start >= 0 && end >= 0) {
3394 ss.frozenWithFocus = true;
3395 }
3396
Gilles Debunne60e21862012-01-30 15:04:14 -08003397 ss.error = getError();
The Android Open Source Project4df24232009-03-05 14:34:35 -08003398
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003399 return ss;
3400 }
3401
3402 return superState;
3403 }
3404
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07003405 void removeMisspelledSpans(Spannable spannable) {
3406 SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(),
3407 SuggestionSpan.class);
3408 for (int i = 0; i < suggestionSpans.length; i++) {
3409 int flags = suggestionSpans[i].getFlags();
3410 if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
3411 && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
3412 spannable.removeSpan(suggestionSpans[i]);
3413 }
3414 }
3415 }
3416
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003417 @Override
3418 public void onRestoreInstanceState(Parcelable state) {
3419 if (!(state instanceof SavedState)) {
3420 super.onRestoreInstanceState(state);
3421 return;
3422 }
3423
3424 SavedState ss = (SavedState)state;
3425 super.onRestoreInstanceState(ss.getSuperState());
3426
3427 // XXX restore buffer type too, as well as lots of other stuff
3428 if (ss.text != null) {
3429 setText(ss.text);
3430 }
3431
3432 if (ss.selStart >= 0 && ss.selEnd >= 0) {
3433 if (mText instanceof Spannable) {
3434 int len = mText.length();
3435
3436 if (ss.selStart > len || ss.selEnd > len) {
3437 String restored = "";
3438
3439 if (ss.text != null) {
3440 restored = "(restored) ";
3441 }
3442
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07003443 Log.e(LOG_TAG, "Saved cursor position " + ss.selStart +
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003444 "/" + ss.selEnd + " out of range for " + restored +
3445 "text " + mText);
3446 } else {
Gilles Debunnec1e79b42012-02-24 17:29:31 -08003447 Selection.setSelection((Spannable) mText, ss.selStart, ss.selEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003448
3449 if (ss.frozenWithFocus) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07003450 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07003451 mEditor.mFrozenWithFocus = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003452 }
3453 }
3454 }
3455 }
The Android Open Source Project4df24232009-03-05 14:34:35 -08003456
3457 if (ss.error != null) {
Romain Guy9bc9fa12009-07-21 16:57:29 -07003458 final CharSequence error = ss.error;
3459 // Display the error later, after the first layout pass
3460 post(new Runnable() {
3461 public void run() {
3462 setError(error);
3463 }
3464 });
The Android Open Source Project4df24232009-03-05 14:34:35 -08003465 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003466 }
3467
3468 /**
3469 * Control whether this text view saves its entire text contents when
3470 * freezing to an icicle, in addition to dynamic state such as cursor
3471 * position. By default this is false, not saving the text. Set to true
3472 * if the text in the text view is not being saved somewhere else in
3473 * persistent storage (such as in a content provider) so that if the
3474 * view is later thawed the user will not lose their data.
3475 *
3476 * @param freezesText Controls whether a frozen icicle should include the
3477 * entire text data: true to include it, false to not.
3478 *
3479 * @attr ref android.R.styleable#TextView_freezesText
3480 */
3481 @android.view.RemotableViewMethod
3482 public void setFreezesText(boolean freezesText) {
3483 mFreezesText = freezesText;
3484 }
3485
3486 /**
3487 * Return whether this text view is including its entire text contents
3488 * in frozen icicles.
3489 *
3490 * @return Returns true if text is included, false if it isn't.
3491 *
3492 * @see #setFreezesText
3493 */
3494 public boolean getFreezesText() {
3495 return mFreezesText;
3496 }
3497
3498 ///////////////////////////////////////////////////////////////////////////
3499
3500 /**
3501 * Sets the Factory used to create new Editables.
3502 */
3503 public final void setEditableFactory(Editable.Factory factory) {
3504 mEditableFactory = factory;
3505 setText(mText);
3506 }
3507
3508 /**
3509 * Sets the Factory used to create new Spannables.
3510 */
3511 public final void setSpannableFactory(Spannable.Factory factory) {
3512 mSpannableFactory = factory;
3513 setText(mText);
3514 }
3515
3516 /**
3517 * Sets the string value of the TextView. TextView <em>does not</em> accept
3518 * HTML-like formatting, which you can do with text strings in XML resource files.
3519 * To style your strings, attach android.text.style.* objects to a
3520 * {@link android.text.SpannableString SpannableString}, or see the
3521 * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
Gilles Debunne21078e42011-08-02 10:22:35 -07003522 * Available Resource Types</a> documentation for an example of setting
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003523 * formatted text in the XML resource file.
3524 *
3525 * @attr ref android.R.styleable#TextView_text
3526 */
3527 @android.view.RemotableViewMethod
3528 public final void setText(CharSequence text) {
3529 setText(text, mBufferType);
3530 }
3531
3532 /**
3533 * Like {@link #setText(CharSequence)},
3534 * except that the cursor position (if any) is retained in the new text.
3535 *
3536 * @param text The new text to place in the text view.
3537 *
3538 * @see #setText(CharSequence)
3539 */
3540 @android.view.RemotableViewMethod
3541 public final void setTextKeepState(CharSequence text) {
3542 setTextKeepState(text, mBufferType);
3543 }
3544
3545 /**
3546 * Sets the text that this TextView is to display (see
3547 * {@link #setText(CharSequence)}) and also sets whether it is stored
3548 * in a styleable/spannable buffer and whether it is editable.
3549 *
3550 * @attr ref android.R.styleable#TextView_text
3551 * @attr ref android.R.styleable#TextView_bufferType
3552 */
3553 public void setText(CharSequence text, BufferType type) {
3554 setText(text, type, true, 0);
3555
3556 if (mCharWrapper != null) {
3557 mCharWrapper.mChars = null;
3558 }
3559 }
3560
3561 private void setText(CharSequence text, BufferType type,
3562 boolean notifyBefore, int oldlen) {
3563 if (text == null) {
3564 text = "";
3565 }
3566
Luca Zanoline0760452011-09-08 12:03:37 +01003567 // If suggestions are not enabled, remove the suggestion spans from the text
3568 if (!isSuggestionsEnabled()) {
3569 text = removeSuggestionSpans(text);
3570 }
3571
Romain Guy939151f2009-04-08 14:22:40 -07003572 if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
3573
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003574 if (text instanceof Spanned &&
3575 ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
Adam Powell282e3772011-08-30 16:51:11 -07003576 if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {
3577 setHorizontalFadingEdgeEnabled(true);
3578 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
3579 } else {
3580 setHorizontalFadingEdgeEnabled(false);
3581 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
3582 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003583 setEllipsize(TextUtils.TruncateAt.MARQUEE);
3584 }
3585
3586 int n = mFilters.length;
3587 for (int i = 0; i < n; i++) {
Gilles Debunnec1714022012-01-17 13:59:23 -08003588 CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003589 if (out != null) {
3590 text = out;
3591 }
3592 }
3593
3594 if (notifyBefore) {
3595 if (mText != null) {
3596 oldlen = mText.length();
3597 sendBeforeTextChanged(mText, 0, oldlen, text.length());
3598 } else {
3599 sendBeforeTextChanged("", 0, 0, text.length());
3600 }
3601 }
3602
3603 boolean needEditableForNotification = false;
3604
3605 if (mListeners != null && mListeners.size() != 0) {
3606 needEditableForNotification = true;
3607 }
3608
Gilles Debunne2d373a12012-04-20 15:32:19 -07003609 if (type == BufferType.EDITABLE || getKeyListener() != null ||
3610 needEditableForNotification) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07003611 createEditorIfNeeded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003612 Editable t = mEditableFactory.newEditable(text);
3613 text = t;
3614 setFilters(t, mFilters);
3615 InputMethodManager imm = InputMethodManager.peekInstance();
3616 if (imm != null) imm.restartInput(this);
3617 } else if (type == BufferType.SPANNABLE || mMovement != null) {
3618 text = mSpannableFactory.newSpannable(text);
3619 } else if (!(text instanceof CharWrapper)) {
3620 text = TextUtils.stringOrSpannedString(text);
3621 }
3622
3623 if (mAutoLinkMask != 0) {
3624 Spannable s2;
3625
3626 if (type == BufferType.EDITABLE || text instanceof Spannable) {
3627 s2 = (Spannable) text;
3628 } else {
3629 s2 = mSpannableFactory.newSpannable(text);
3630 }
3631
3632 if (Linkify.addLinks(s2, mAutoLinkMask)) {
3633 text = s2;
3634 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
3635
3636 /*
3637 * We must go ahead and set the text before changing the
3638 * movement method, because setMovementMethod() may call
3639 * setText() again to try to upgrade the buffer type.
3640 */
3641 mText = text;
3642
Gilles Debunnecbcb3452010-12-17 15:31:02 -08003643 // Do not change the movement method for text that support text selection as it
3644 // would prevent an arbitrary cursor displacement.
Gilles Debunnebb588da2011-07-11 18:26:19 -07003645 if (mLinksClickable && !textCanBeSelected()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003646 setMovementMethod(LinkMovementMethod.getInstance());
3647 }
3648 }
3649 }
3650
3651 mBufferType = type;
3652 mText = text;
3653
Adam Powell7f8f79a2011-07-07 18:35:54 -07003654 if (mTransformation == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003655 mTransformed = text;
Adam Powell7f8f79a2011-07-07 18:35:54 -07003656 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003657 mTransformed = mTransformation.getTransformation(text, this);
Adam Powell7f8f79a2011-07-07 18:35:54 -07003658 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003659
3660 final int textLength = text.length();
3661
Adam Powell7f8f79a2011-07-07 18:35:54 -07003662 if (text instanceof Spannable && !mAllowTransformationLengthChange) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003663 Spannable sp = (Spannable) text;
3664
Gilles Debunnec62589c2012-04-12 14:50:23 -07003665 // Remove any ChangeWatchers that might have come from other TextViews.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003666 final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
3667 final int count = watchers.length;
Gilles Debunnec62589c2012-04-12 14:50:23 -07003668 for (int i = 0; i < count; i++) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003669 sp.removeSpan(watchers[i]);
Gilles Debunnec62589c2012-04-12 14:50:23 -07003670 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003671
Gilles Debunnec62589c2012-04-12 14:50:23 -07003672 if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003673
3674 sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE |
Gilles Debunne60e21862012-01-30 15:04:14 -08003675 (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003676
Gilles Debunnec62589c2012-04-12 14:50:23 -07003677 if (mEditor != null) mEditor.addSpanWatchers(sp);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003678
3679 if (mTransformation != null) {
3680 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003681 }
3682
3683 if (mMovement != null) {
3684 mMovement.initialize(this, (Spannable) text);
3685
3686 /*
3687 * Initializing the movement method will have set the
3688 * selection, so reset mSelectionMoved to keep that from
3689 * interfering with the normal on-focus selection-setting.
3690 */
Gilles Debunne2d373a12012-04-20 15:32:19 -07003691 if (mEditor != null) mEditor.mSelectionMoved = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003692 }
3693 }
3694
3695 if (mLayout != null) {
3696 checkForRelayout();
3697 }
3698
3699 sendOnTextChanged(text, 0, oldlen, textLength);
3700 onTextChanged(text, 0, oldlen, textLength);
3701
3702 if (needEditableForNotification) {
3703 sendAfterTextChanged((Editable) text);
3704 }
Gilles Debunne05336272010-07-09 20:13:45 -07003705
Gilles Debunnebaaace52010-10-01 15:47:13 -07003706 // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
Gilles Debunne2d373a12012-04-20 15:32:19 -07003707 if (mEditor != null) mEditor.prepareCursorControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003708 }
3709
3710 /**
3711 * Sets the TextView to display the specified slice of the specified
3712 * char array. You must promise that you will not change the contents
3713 * of the array except for right before another call to setText(),
3714 * since the TextView has no way to know that the text
3715 * has changed and that it needs to invalidate and re-layout.
3716 */
3717 public final void setText(char[] text, int start, int len) {
3718 int oldlen = 0;
3719
3720 if (start < 0 || len < 0 || start + len > text.length) {
3721 throw new IndexOutOfBoundsException(start + ", " + len);
3722 }
3723
3724 /*
3725 * We must do the before-notification here ourselves because if
3726 * the old text is a CharWrapper we destroy it before calling
3727 * into the normal path.
3728 */
3729 if (mText != null) {
3730 oldlen = mText.length();
3731 sendBeforeTextChanged(mText, 0, oldlen, len);
3732 } else {
3733 sendBeforeTextChanged("", 0, 0, len);
3734 }
3735
3736 if (mCharWrapper == null) {
3737 mCharWrapper = new CharWrapper(text, start, len);
3738 } else {
3739 mCharWrapper.set(text, start, len);
3740 }
3741
3742 setText(mCharWrapper, mBufferType, false, oldlen);
3743 }
3744
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003745 /**
3746 * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)},
3747 * except that the cursor position (if any) is retained in the new text.
3748 *
3749 * @see #setText(CharSequence, android.widget.TextView.BufferType)
3750 */
3751 public final void setTextKeepState(CharSequence text, BufferType type) {
3752 int start = getSelectionStart();
3753 int end = getSelectionEnd();
3754 int len = text.length();
3755
3756 setText(text, type);
3757
3758 if (start >= 0 || end >= 0) {
3759 if (mText instanceof Spannable) {
3760 Selection.setSelection((Spannable) mText,
3761 Math.max(0, Math.min(start, len)),
3762 Math.max(0, Math.min(end, len)));
3763 }
3764 }
3765 }
3766
3767 @android.view.RemotableViewMethod
3768 public final void setText(int resid) {
3769 setText(getContext().getResources().getText(resid));
3770 }
3771
3772 public final void setText(int resid, BufferType type) {
3773 setText(getContext().getResources().getText(resid), type);
3774 }
3775
3776 /**
3777 * Sets the text to be displayed when the text of the TextView is empty.
3778 * Null means to use the normal empty text. The hint does not currently
3779 * participate in determining the size of the view.
3780 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003781 * @attr ref android.R.styleable#TextView_hint
3782 */
3783 @android.view.RemotableViewMethod
3784 public final void setHint(CharSequence hint) {
3785 mHint = TextUtils.stringOrSpannedString(hint);
3786
3787 if (mLayout != null) {
3788 checkForRelayout();
3789 }
3790
Romain Guy4dc4f732009-06-19 15:16:40 -07003791 if (mText.length() == 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003792 invalidate();
Romain Guy4dc4f732009-06-19 15:16:40 -07003793 }
Gilles Debunne626c3162012-02-14 15:46:41 -08003794
Gilles Debunne33b7de852012-03-12 11:57:48 -07003795 // Invalidate display list if hint is currently used
Gilles Debunne60e21862012-01-30 15:04:14 -08003796 if (mEditor != null && mText.length() == 0 && mHint != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07003797 mEditor.invalidateTextDisplayList();
Gilles Debunne60e21862012-01-30 15:04:14 -08003798 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003799 }
3800
3801 /**
3802 * Sets the text to be displayed when the text of the TextView is empty,
3803 * from a resource.
3804 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003805 * @attr ref android.R.styleable#TextView_hint
3806 */
3807 @android.view.RemotableViewMethod
3808 public final void setHint(int resid) {
3809 setHint(getContext().getResources().getText(resid));
3810 }
3811
3812 /**
3813 * Returns the hint that is displayed when the text of the TextView
3814 * is empty.
3815 *
3816 * @attr ref android.R.styleable#TextView_hint
3817 */
3818 @ViewDebug.CapturedViewProperty
3819 public CharSequence getHint() {
3820 return mHint;
3821 }
3822
Gilles Debunned88876a2012-03-16 17:34:04 -07003823 boolean isSingleLine() {
3824 return mSingleLine;
3825 }
3826
Gilles Debunne3784a7f2011-07-15 13:49:38 -07003827 private static boolean isMultilineInputType(int type) {
Gilles Debunne91a08cf2010-11-08 17:34:49 -08003828 return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) ==
3829 (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
3830 }
3831
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003832 /**
Gilles Debunned88876a2012-03-16 17:34:04 -07003833 * Removes the suggestion spans.
3834 */
3835 CharSequence removeSuggestionSpans(CharSequence text) {
3836 if (text instanceof Spanned) {
3837 Spannable spannable;
3838 if (text instanceof Spannable) {
3839 spannable = (Spannable) text;
3840 } else {
3841 spannable = new SpannableString(text);
3842 text = spannable;
3843 }
3844
3845 SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
3846 for (int i = 0; i < spans.length; i++) {
3847 spannable.removeSpan(spans[i]);
3848 }
3849 }
3850 return text;
3851 }
3852
3853 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003854 * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This
3855 * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)},
3856 * to match the given content type. If the given content type is {@link EditorInfo#TYPE_NULL}
3857 * then a soft keyboard will not be displayed for this text view.
3858 *
3859 * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be
3860 * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input
3861 * type.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003862 *
3863 * @see #getInputType()
3864 * @see #setRawInputType(int)
3865 * @see android.text.InputType
3866 * @attr ref android.R.styleable#TextView_inputType
3867 */
3868 public void setInputType(int type) {
Gilles Debunne60e21862012-01-30 15:04:14 -08003869 final boolean wasPassword = isPasswordInputType(getInputType());
3870 final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003871 setInputType(type, false);
Bjorn Bringertad8da912009-09-17 10:47:35 +01003872 final boolean isPassword = isPasswordInputType(type);
3873 final boolean isVisiblePassword = isVisiblePasswordInputType(type);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003874 boolean forceUpdate = false;
3875 if (isPassword) {
3876 setTransformationMethod(PasswordTransformationMethod.getInstance());
Raph Leviend570e892012-05-09 11:45:34 -07003877 setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
Bjorn Bringertad8da912009-09-17 10:47:35 +01003878 } else if (isVisiblePassword) {
Amith Yamasania8c0edb2009-09-27 16:51:21 -07003879 if (mTransformation == PasswordTransformationMethod.getInstance()) {
3880 forceUpdate = true;
3881 }
Raph Leviend570e892012-05-09 11:45:34 -07003882 setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
Bjorn Bringertad8da912009-09-17 10:47:35 +01003883 } else if (wasPassword || wasVisiblePassword) {
3884 // not in password mode, clean up typeface and transformation
Raph Leviend570e892012-05-09 11:45:34 -07003885 setTypefaceFromAttrs(null /* fontFamily */, -1, -1);
Bjorn Bringertad8da912009-09-17 10:47:35 +01003886 if (mTransformation == PasswordTransformationMethod.getInstance()) {
3887 forceUpdate = true;
3888 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003889 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07003890
Gilles Debunne91a08cf2010-11-08 17:34:49 -08003891 boolean singleLine = !isMultilineInputType(type);
Gilles Debunne2d373a12012-04-20 15:32:19 -07003892
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003893 // We need to update the single line mode if it has changed or we
3894 // were previously in password mode.
Gilles Debunne91a08cf2010-11-08 17:34:49 -08003895 if (mSingleLine != singleLine || forceUpdate) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003896 // Change single line mode, but only change the transformation if
3897 // we are not in password mode.
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003898 applySingleLine(singleLine, !isPassword, true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003899 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07003900
Luca Zanoline0760452011-09-08 12:03:37 +01003901 if (!isSuggestionsEnabled()) {
3902 mText = removeSuggestionSpans(mText);
3903 }
3904
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003905 InputMethodManager imm = InputMethodManager.peekInstance();
3906 if (imm != null) imm.restartInput(this);
3907 }
3908
Gilles Debunne0dcad2b2010-10-15 16:29:25 -07003909 /**
3910 * It would be better to rely on the input type for everything. A password inputType should have
3911 * a password transformation. We should hence use isPasswordInputType instead of this method.
3912 *
3913 * We should:
3914 * - Call setInputType in setKeyListener instead of changing the input type directly (which
3915 * would install the correct transformation).
3916 * - Refuse the installation of a non-password transformation in setTransformation if the input
3917 * type is password.
3918 *
3919 * However, this is like this for legacy reasons and we cannot break existing apps. This method
3920 * is useful since it matches what the user can see (obfuscated text or not).
3921 *
3922 * @return true if the current transformation method is of the password type.
3923 */
3924 private boolean hasPasswordTransformationMethod() {
3925 return mTransformation instanceof PasswordTransformationMethod;
3926 }
3927
Gilles Debunne3784a7f2011-07-15 13:49:38 -07003928 private static boolean isPasswordInputType(int inputType) {
Gilles Debunned7483bf2010-11-10 10:47:45 -08003929 final int variation =
3930 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
Bjorn Bringertad8da912009-09-17 10:47:35 +01003931 return variation
Gilles Debunned7483bf2010-11-10 10:47:45 -08003932 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)
3933 || variation
Ken Wakasa82d731a2010-12-24 23:42:41 +09003934 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD)
3935 || variation
3936 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
Bjorn Bringertad8da912009-09-17 10:47:35 +01003937 }
3938
Gilles Debunne3784a7f2011-07-15 13:49:38 -07003939 private static boolean isVisiblePasswordInputType(int inputType) {
Gilles Debunned7483bf2010-11-10 10:47:45 -08003940 final int variation =
3941 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
Bjorn Bringertad8da912009-09-17 10:47:35 +01003942 return variation
Gilles Debunned7483bf2010-11-10 10:47:45 -08003943 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
Bjorn Bringertad8da912009-09-17 10:47:35 +01003944 }
3945
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003946 /**
3947 * Directly change the content type integer of the text view, without
3948 * modifying any other state.
3949 * @see #setInputType(int)
3950 * @see android.text.InputType
3951 * @attr ref android.R.styleable#TextView_inputType
3952 */
3953 public void setRawInputType(int type) {
Gilles Debunne60e21862012-01-30 15:04:14 -08003954 if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
Gilles Debunne5fae9962012-05-08 14:53:20 -07003955 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07003956 mEditor.mInputType = type;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003957 }
3958
3959 private void setInputType(int type, boolean direct) {
3960 final int cls = type & EditorInfo.TYPE_MASK_CLASS;
3961 KeyListener input;
3962 if (cls == EditorInfo.TYPE_CLASS_TEXT) {
Gilles Debunnee67b58a2010-08-31 15:55:31 -07003963 boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003964 TextKeyListener.Capitalize cap;
3965 if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
3966 cap = TextKeyListener.Capitalize.CHARACTERS;
3967 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
3968 cap = TextKeyListener.Capitalize.WORDS;
3969 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
3970 cap = TextKeyListener.Capitalize.SENTENCES;
3971 } else {
3972 cap = TextKeyListener.Capitalize.NONE;
3973 }
3974 input = TextKeyListener.getInstance(autotext, cap);
3975 } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
3976 input = DigitsKeyListener.getInstance(
3977 (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
3978 (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
3979 } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
3980 switch (type & EditorInfo.TYPE_MASK_VARIATION) {
3981 case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
3982 input = DateKeyListener.getInstance();
3983 break;
3984 case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
3985 input = TimeKeyListener.getInstance();
3986 break;
3987 default:
3988 input = DateTimeKeyListener.getInstance();
3989 break;
3990 }
3991 } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
3992 input = DialerKeyListener.getInstance();
3993 } else {
3994 input = TextKeyListener.getInstance();
3995 }
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07003996 setRawInputType(type);
Gilles Debunne60e21862012-01-30 15:04:14 -08003997 if (direct) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07003998 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07003999 mEditor.mKeyListener = input;
Gilles Debunne60e21862012-01-30 15:04:14 -08004000 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004001 setKeyListenerOnly(input);
4002 }
4003 }
4004
4005 /**
Gilles Debunne60e21862012-01-30 15:04:14 -08004006 * Get the type of the editable content.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004007 *
4008 * @see #setInputType(int)
4009 * @see android.text.InputType
4010 */
4011 public int getInputType() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004012 return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004013 }
4014
4015 /**
4016 * Change the editor type integer associated with the text view, which
4017 * will be reported to an IME with {@link EditorInfo#imeOptions} when it
4018 * has focus.
4019 * @see #getImeOptions
4020 * @see android.view.inputmethod.EditorInfo
4021 * @attr ref android.R.styleable#TextView_imeOptions
4022 */
4023 public void setImeOptions(int imeOptions) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004024 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004025 mEditor.createInputContentTypeIfNeeded();
4026 mEditor.mInputContentType.imeOptions = imeOptions;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004027 }
4028
4029 /**
4030 * Get the type of the IME editor.
4031 *
4032 * @see #setImeOptions(int)
4033 * @see android.view.inputmethod.EditorInfo
4034 */
4035 public int getImeOptions() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004036 return mEditor != null && mEditor.mInputContentType != null
4037 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004038 }
4039
4040 /**
4041 * Change the custom IME action associated with the text view, which
4042 * will be reported to an IME with {@link EditorInfo#actionLabel}
4043 * and {@link EditorInfo#actionId} when it has focus.
4044 * @see #getImeActionLabel
4045 * @see #getImeActionId
4046 * @see android.view.inputmethod.EditorInfo
4047 * @attr ref android.R.styleable#TextView_imeActionLabel
4048 * @attr ref android.R.styleable#TextView_imeActionId
4049 */
4050 public void setImeActionLabel(CharSequence label, int actionId) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004051 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004052 mEditor.createInputContentTypeIfNeeded();
4053 mEditor.mInputContentType.imeActionLabel = label;
4054 mEditor.mInputContentType.imeActionId = actionId;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004055 }
4056
4057 /**
4058 * Get the IME action label previous set with {@link #setImeActionLabel}.
4059 *
4060 * @see #setImeActionLabel
4061 * @see android.view.inputmethod.EditorInfo
4062 */
4063 public CharSequence getImeActionLabel() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004064 return mEditor != null && mEditor.mInputContentType != null
4065 ? mEditor.mInputContentType.imeActionLabel : null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004066 }
4067
4068 /**
4069 * Get the IME action ID previous set with {@link #setImeActionLabel}.
4070 *
4071 * @see #setImeActionLabel
4072 * @see android.view.inputmethod.EditorInfo
4073 */
4074 public int getImeActionId() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004075 return mEditor != null && mEditor.mInputContentType != null
4076 ? mEditor.mInputContentType.imeActionId : 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004077 }
4078
4079 /**
4080 * Set a special listener to be called when an action is performed
4081 * on the text view. This will be called when the enter key is pressed,
4082 * or when an action supplied to the IME is selected by the user. Setting
4083 * this means that the normal hard key event will not insert a newline
4084 * into the text view, even if it is multi-line; holding down the ALT
4085 * modifier will, however, allow the user to insert a newline character.
4086 */
4087 public void setOnEditorActionListener(OnEditorActionListener l) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004088 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004089 mEditor.createInputContentTypeIfNeeded();
4090 mEditor.mInputContentType.onEditorActionListener = l;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004091 }
Gilles Debunne60e21862012-01-30 15:04:14 -08004092
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004093 /**
4094 * Called when an attached input method calls
4095 * {@link InputConnection#performEditorAction(int)
4096 * InputConnection.performEditorAction()}
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07004097 * for this text view. The default implementation will call your action
4098 * listener supplied to {@link #setOnEditorActionListener}, or perform
4099 * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
Dianne Hackborndea3ef72010-10-28 14:24:22 -07004100 * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS
4101 * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07004102 * EditorInfo.IME_ACTION_DONE}.
Gilles Debunne2d373a12012-04-20 15:32:19 -07004103 *
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07004104 * <p>For backwards compatibility, if no IME options have been set and the
4105 * text view would not normally advance focus on enter, then
4106 * the NEXT and DONE actions received here will be turned into an enter
4107 * key down/up pair to go through the normal key handling.
Gilles Debunne2d373a12012-04-20 15:32:19 -07004108 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004109 * @param actionCode The code of the action being performed.
Gilles Debunne2d373a12012-04-20 15:32:19 -07004110 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004111 * @see #setOnEditorActionListener
4112 */
4113 public void onEditorAction(int actionCode) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004114 final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004115 if (ict != null) {
4116 if (ict.onEditorActionListener != null) {
4117 if (ict.onEditorActionListener.onEditorAction(this,
4118 actionCode, null)) {
4119 return;
4120 }
4121 }
Gilles Debunne64794482011-11-30 15:45:28 -08004122
The Android Open Source Project4df24232009-03-05 14:34:35 -08004123 // This is the handling for some default action.
4124 // Note that for backwards compatibility we don't do this
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004125 // default handling if explicit ime options have not been given,
The Android Open Source Project10592532009-03-18 17:39:46 -07004126 // instead turning this into the normal enter key codes that an
The Android Open Source Project4df24232009-03-05 14:34:35 -08004127 // app may be expecting.
4128 if (actionCode == EditorInfo.IME_ACTION_NEXT) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004129 View v = focusSearch(FOCUS_FORWARD);
The Android Open Source Project4df24232009-03-05 14:34:35 -08004130 if (v != null) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004131 if (!v.requestFocus(FOCUS_FORWARD)) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08004132 throw new IllegalStateException("focus search returned a view " +
4133 "that wasn't able to take focus!");
4134 }
4135 }
4136 return;
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004137
Dianne Hackborndea3ef72010-10-28 14:24:22 -07004138 } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004139 View v = focusSearch(FOCUS_BACKWARD);
Dianne Hackborndea3ef72010-10-28 14:24:22 -07004140 if (v != null) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004141 if (!v.requestFocus(FOCUS_BACKWARD)) {
Dianne Hackborndea3ef72010-10-28 14:24:22 -07004142 throw new IllegalStateException("focus search returned a view " +
4143 "that wasn't able to take focus!");
4144 }
4145 }
4146 return;
4147
The Android Open Source Project4df24232009-03-05 14:34:35 -08004148 } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
4149 InputMethodManager imm = InputMethodManager.peekInstance();
Gilles Debunne17d31de2011-01-27 11:02:18 -08004150 if (imm != null && imm.isActive(this)) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08004151 imm.hideSoftInputFromWindow(getWindowToken(), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004152 }
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07004153 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004154 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004155 }
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004156
Jeff Browna175a5b2012-02-15 19:18:31 -08004157 ViewRootImpl viewRootImpl = getViewRootImpl();
4158 if (viewRootImpl != null) {
The Android Open Source Project10592532009-03-18 17:39:46 -07004159 long eventTime = SystemClock.uptimeMillis();
Jeff Browna175a5b2012-02-15 19:18:31 -08004160 viewRootImpl.dispatchKeyFromIme(
The Android Open Source Project10592532009-03-18 17:39:46 -07004161 new KeyEvent(eventTime, eventTime,
Jeff Brown6b53e8d2010-11-10 16:03:06 -08004162 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
4163 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
The Android Open Source Project10592532009-03-18 17:39:46 -07004164 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
Jeff Browna175a5b2012-02-15 19:18:31 -08004165 | KeyEvent.FLAG_EDITOR_ACTION));
4166 viewRootImpl.dispatchKeyFromIme(
The Android Open Source Project10592532009-03-18 17:39:46 -07004167 new KeyEvent(SystemClock.uptimeMillis(), eventTime,
Jeff Brown6b53e8d2010-11-10 16:03:06 -08004168 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
4169 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
The Android Open Source Project10592532009-03-18 17:39:46 -07004170 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
Jeff Browna175a5b2012-02-15 19:18:31 -08004171 | KeyEvent.FLAG_EDITOR_ACTION));
The Android Open Source Project10592532009-03-18 17:39:46 -07004172 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004173 }
Gilles Debunne64794482011-11-30 15:45:28 -08004174
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004175 /**
4176 * Set the private content type of the text, which is the
4177 * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
4178 * field that will be filled in when creating an input connection.
4179 *
4180 * @see #getPrivateImeOptions()
4181 * @see EditorInfo#privateImeOptions
4182 * @attr ref android.R.styleable#TextView_privateImeOptions
4183 */
4184 public void setPrivateImeOptions(String type) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004185 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004186 mEditor.createInputContentTypeIfNeeded();
4187 mEditor.mInputContentType.privateImeOptions = type;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004188 }
4189
4190 /**
4191 * Get the private type of the content.
4192 *
4193 * @see #setPrivateImeOptions(String)
4194 * @see EditorInfo#privateImeOptions
4195 */
4196 public String getPrivateImeOptions() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004197 return mEditor != null && mEditor.mInputContentType != null
4198 ? mEditor.mInputContentType.privateImeOptions : null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004199 }
4200
4201 /**
4202 * Set the extra input data of the text, which is the
4203 * {@link EditorInfo#extras TextBoxAttribute.extras}
4204 * Bundle that will be filled in when creating an input connection. The
4205 * given integer is the resource ID of an XML resource holding an
4206 * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
4207 *
Gilles Debunne2d373a12012-04-20 15:32:19 -07004208 * @see #getInputExtras(boolean)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004209 * @see EditorInfo#extras
4210 * @attr ref android.R.styleable#TextView_editorExtras
4211 */
Gilles Debunne60e21862012-01-30 15:04:14 -08004212 public void setInputExtras(int xmlResId) throws XmlPullParserException, IOException {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004213 createEditorIfNeeded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004214 XmlResourceParser parser = getResources().getXml(xmlResId);
Gilles Debunne2d373a12012-04-20 15:32:19 -07004215 mEditor.createInputContentTypeIfNeeded();
4216 mEditor.mInputContentType.extras = new Bundle();
4217 getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004218 }
4219
4220 /**
4221 * Retrieve the input extras currently associated with the text view, which
4222 * can be viewed as well as modified.
4223 *
4224 * @param create If true, the extras will be created if they don't already
4225 * exist. Otherwise, null will be returned if none have been created.
Gilles Debunnee15b3582010-06-16 15:17:21 -07004226 * @see #setInputExtras(int)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004227 * @see EditorInfo#extras
4228 * @attr ref android.R.styleable#TextView_editorExtras
4229 */
4230 public Bundle getInputExtras(boolean create) {
Gilles Debunne60e21862012-01-30 15:04:14 -08004231 if (mEditor == null && !create) return null;
Gilles Debunne5fae9962012-05-08 14:53:20 -07004232 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004233 if (mEditor.mInputContentType == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004234 if (!create) return null;
Gilles Debunne2d373a12012-04-20 15:32:19 -07004235 mEditor.createInputContentTypeIfNeeded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004236 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07004237 if (mEditor.mInputContentType.extras == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004238 if (!create) return null;
Gilles Debunne2d373a12012-04-20 15:32:19 -07004239 mEditor.mInputContentType.extras = new Bundle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004240 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07004241 return mEditor.mInputContentType.extras;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004242 }
4243
4244 /**
4245 * Returns the error message that was set to be displayed with
4246 * {@link #setError}, or <code>null</code> if no error was set
4247 * or if it the error was cleared by the widget after user input.
4248 */
4249 public CharSequence getError() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004250 return mEditor == null ? null : mEditor.mError;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004251 }
4252
4253 /**
4254 * Sets the right-hand compound drawable of the TextView to the "error"
4255 * icon and sets an error message that will be displayed in a popup when
4256 * the TextView has focus. The icon and error message will be reset to
4257 * null when any key events cause changes to the TextView's text. If the
4258 * <code>error</code> is <code>null</code>, the error message and icon
4259 * will be cleared.
4260 */
4261 @android.view.RemotableViewMethod
4262 public void setError(CharSequence error) {
4263 if (error == null) {
4264 setError(null, null);
4265 } else {
4266 Drawable dr = getContext().getResources().
Gilles Debunnea85467b2011-01-19 16:53:31 -08004267 getDrawable(com.android.internal.R.drawable.indicator_input_error);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004268
4269 dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
4270 setError(error, dr);
4271 }
4272 }
4273
4274 /**
4275 * Sets the right-hand compound drawable of the TextView to the specified
4276 * icon and sets an error message that will be displayed in a popup when
4277 * the TextView has focus. The icon and error message will be reset to
4278 * null when any key events cause changes to the TextView's text. The
4279 * drawable must already have had {@link Drawable#setBounds} set on it.
4280 * If the <code>error</code> is <code>null</code>, the error message will
4281 * be cleared (and you should provide a <code>null</code> icon as well).
4282 */
4283 public void setError(CharSequence error, Drawable icon) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004284 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004285 mEditor.setError(error, icon);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004286 }
4287
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004288 @Override
4289 protected boolean setFrame(int l, int t, int r, int b) {
4290 boolean result = super.setFrame(l, t, r, b);
4291
Gilles Debunne2d373a12012-04-20 15:32:19 -07004292 if (mEditor != null) mEditor.setFrame();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004293
Romain Guy986003d2009-03-25 17:42:35 -07004294 restartMarqueeIfNeeded();
4295
4296 return result;
4297 }
4298
4299 private void restartMarqueeIfNeeded() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004300 if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
4301 mRestartMarquee = false;
4302 startMarquee();
4303 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004304 }
4305
4306 /**
4307 * Sets the list of input filters that will be used if the buffer is
Gilles Debunne60e21862012-01-30 15:04:14 -08004308 * Editable. Has no effect otherwise.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004309 *
4310 * @attr ref android.R.styleable#TextView_maxLength
4311 */
4312 public void setFilters(InputFilter[] filters) {
4313 if (filters == null) {
4314 throw new IllegalArgumentException();
4315 }
4316
4317 mFilters = filters;
4318
4319 if (mText instanceof Editable) {
4320 setFilters((Editable) mText, filters);
4321 }
4322 }
4323
4324 /**
4325 * Sets the list of input filters on the specified Editable,
4326 * and includes mInput in the list if it is an InputFilter.
4327 */
4328 private void setFilters(Editable e, InputFilter[] filters) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004329 if (mEditor != null && mEditor.mKeyListener instanceof InputFilter) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004330 InputFilter[] nf = new InputFilter[filters.length + 1];
4331
4332 System.arraycopy(filters, 0, nf, 0, filters.length);
Gilles Debunne2d373a12012-04-20 15:32:19 -07004333 nf[filters.length] = (InputFilter) mEditor.mKeyListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004334
4335 e.setFilters(nf);
4336 } else {
4337 e.setFilters(filters);
4338 }
4339 }
4340
4341 /**
4342 * Returns the current list of input filters.
Gilles Debunnef03acef2012-04-30 19:26:19 -07004343 *
4344 * @attr ref android.R.styleable#TextView_maxLength
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004345 */
4346 public InputFilter[] getFilters() {
4347 return mFilters;
4348 }
4349
4350 /////////////////////////////////////////////////////////////////////////
4351
Gilles Debunned88876a2012-03-16 17:34:04 -07004352 int getVerticalOffset(boolean forceNormal) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004353 int voffset = 0;
4354 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
4355
4356 Layout l = mLayout;
4357 if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
4358 l = mHintLayout;
4359 }
4360
4361 if (gravity != Gravity.TOP) {
4362 int boxht;
4363
4364 if (l == mHintLayout) {
4365 boxht = getMeasuredHeight() - getCompoundPaddingTop() -
4366 getCompoundPaddingBottom();
4367 } else {
4368 boxht = getMeasuredHeight() - getExtendedPaddingTop() -
4369 getExtendedPaddingBottom();
4370 }
4371 int textht = l.getHeight();
4372
4373 if (textht < boxht) {
4374 if (gravity == Gravity.BOTTOM)
4375 voffset = boxht - textht;
4376 else // (gravity == Gravity.CENTER_VERTICAL)
4377 voffset = (boxht - textht) >> 1;
4378 }
4379 }
4380 return voffset;
4381 }
4382
4383 private int getBottomVerticalOffset(boolean forceNormal) {
4384 int voffset = 0;
4385 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
4386
4387 Layout l = mLayout;
4388 if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
4389 l = mHintLayout;
4390 }
4391
4392 if (gravity != Gravity.BOTTOM) {
4393 int boxht;
4394
4395 if (l == mHintLayout) {
4396 boxht = getMeasuredHeight() - getCompoundPaddingTop() -
4397 getCompoundPaddingBottom();
4398 } else {
4399 boxht = getMeasuredHeight() - getExtendedPaddingTop() -
4400 getExtendedPaddingBottom();
4401 }
4402 int textht = l.getHeight();
4403
4404 if (textht < boxht) {
4405 if (gravity == Gravity.TOP)
4406 voffset = boxht - textht;
4407 else // (gravity == Gravity.CENTER_VERTICAL)
4408 voffset = (boxht - textht) >> 1;
4409 }
4410 }
4411 return voffset;
4412 }
4413
Gilles Debunned88876a2012-03-16 17:34:04 -07004414 void invalidateCursorPath() {
Gilles Debunne83051b82012-02-24 20:01:13 -08004415 if (mHighlightPathBogus) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004416 invalidateCursor();
4417 } else {
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004418 final int horizontalPadding = getCompoundPaddingLeft();
4419 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004420
Gilles Debunne2d373a12012-04-20 15:32:19 -07004421 if (mEditor.mCursorCount == 0) {
Gilles Debunne60e21862012-01-30 15:04:14 -08004422 synchronized (TEMP_RECTF) {
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004423 /*
4424 * The reason for this concern about the thickness of the
4425 * cursor and doing the floor/ceil on the coordinates is that
4426 * some EditTexts (notably textfields in the Browser) have
4427 * anti-aliased text where not all the characters are
4428 * necessarily at integer-multiple locations. This should
4429 * make sure the entire cursor gets invalidated instead of
4430 * sometimes missing half a pixel.
4431 */
4432 float thick = FloatMath.ceil(mTextPaint.getStrokeWidth());
4433 if (thick < 1.0f) {
4434 thick = 1.0f;
4435 }
4436
4437 thick /= 2.0f;
4438
Gilles Debunne83051b82012-02-24 20:01:13 -08004439 // mHighlightPath is guaranteed to be non null at that point.
4440 mHighlightPath.computeBounds(TEMP_RECTF, false);
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004441
Gilles Debunne60e21862012-01-30 15:04:14 -08004442 invalidate((int) FloatMath.floor(horizontalPadding + TEMP_RECTF.left - thick),
4443 (int) FloatMath.floor(verticalPadding + TEMP_RECTF.top - thick),
4444 (int) FloatMath.ceil(horizontalPadding + TEMP_RECTF.right + thick),
4445 (int) FloatMath.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004446 }
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004447 } else {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004448 for (int i = 0; i < mEditor.mCursorCount; i++) {
4449 Rect bounds = mEditor.mCursorDrawable[i].getBounds();
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004450 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
4451 bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
4452 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004453 }
4454 }
4455 }
4456
Gilles Debunned88876a2012-03-16 17:34:04 -07004457 void invalidateCursor() {
Gilles Debunne05336272010-07-09 20:13:45 -07004458 int where = getSelectionEnd();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004459
4460 invalidateCursor(where, where, where);
4461 }
4462
4463 private void invalidateCursor(int a, int b, int c) {
Gilles Debunne8615ac92011-11-29 15:25:03 -08004464 if (a >= 0 || b >= 0 || c >= 0) {
4465 int start = Math.min(Math.min(a, b), c);
4466 int end = Math.max(Math.max(a, b), c);
Gilles Debunne961ebb92011-12-12 10:16:04 -08004467 invalidateRegion(start, end, true /* Also invalidates blinking cursor */);
Gilles Debunne8615ac92011-11-29 15:25:03 -08004468 }
4469 }
4470
4471 /**
4472 * Invalidates the region of text enclosed between the start and end text offsets.
Gilles Debunne8615ac92011-11-29 15:25:03 -08004473 */
Gilles Debunne961ebb92011-12-12 10:16:04 -08004474 void invalidateRegion(int start, int end, boolean invalidateCursor) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004475 if (mLayout == null) {
4476 invalidate();
4477 } else {
Gilles Debunne8615ac92011-11-29 15:25:03 -08004478 int lineStart = mLayout.getLineForOffset(start);
4479 int top = mLayout.getLineTop(lineStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004480
4481 // This is ridiculous, but the descent from the line above
4482 // can hang down into the line we really want to redraw,
4483 // so we have to invalidate part of the line above to make
4484 // sure everything that needs to be redrawn really is.
4485 // (But not the whole line above, because that would cause
4486 // the same problem with the descenders on the line above it!)
Gilles Debunne8615ac92011-11-29 15:25:03 -08004487 if (lineStart > 0) {
4488 top -= mLayout.getLineDescent(lineStart - 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004489 }
4490
Gilles Debunne8615ac92011-11-29 15:25:03 -08004491 int lineEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004492
Gilles Debunne8615ac92011-11-29 15:25:03 -08004493 if (start == end)
4494 lineEnd = lineStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004495 else
Gilles Debunne8615ac92011-11-29 15:25:03 -08004496 lineEnd = mLayout.getLineForOffset(end);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004497
Gilles Debunne8615ac92011-11-29 15:25:03 -08004498 int bottom = mLayout.getLineBottom(lineEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004499
Gilles Debunne83051b82012-02-24 20:01:13 -08004500 // mEditor can be null in case selection is set programmatically.
4501 if (invalidateCursor && mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004502 for (int i = 0; i < mEditor.mCursorCount; i++) {
4503 Rect bounds = mEditor.mCursorDrawable[i].getBounds();
Gilles Debunne961ebb92011-12-12 10:16:04 -08004504 top = Math.min(top, bounds.top);
4505 bottom = Math.max(bottom, bounds.bottom);
4506 }
4507 }
4508
Gilles Debunne8615ac92011-11-29 15:25:03 -08004509 final int compoundPaddingLeft = getCompoundPaddingLeft();
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004510 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
Gilles Debunne8615ac92011-11-29 15:25:03 -08004511
4512 int left, right;
Gilles Debunne961ebb92011-12-12 10:16:04 -08004513 if (lineStart == lineEnd && !invalidateCursor) {
Gilles Debunne8615ac92011-11-29 15:25:03 -08004514 left = (int) mLayout.getPrimaryHorizontal(start);
4515 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0);
4516 left += compoundPaddingLeft;
4517 right += compoundPaddingLeft;
4518 } else {
4519 // Rectangle bounding box when the region spans several lines
4520 left = compoundPaddingLeft;
4521 right = getWidth() - getCompoundPaddingRight();
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004522 }
4523
Gilles Debunne8615ac92011-11-29 15:25:03 -08004524 invalidate(mScrollX + left, verticalPadding + top,
4525 mScrollX + right, verticalPadding + bottom);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004526 }
4527 }
4528
4529 private void registerForPreDraw() {
Gilles Debunne2e37d622012-01-27 13:54:00 -08004530 if (!mPreDrawRegistered) {
4531 getViewTreeObserver().addOnPreDrawListener(this);
4532 mPreDrawRegistered = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004533 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004534 }
4535
4536 /**
4537 * {@inheritDoc}
4538 */
4539 public boolean onPreDraw() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004540 if (mLayout == null) {
4541 assumeLayout();
4542 }
4543
4544 boolean changed = false;
4545
4546 if (mMovement != null) {
Gilles Debunne05336272010-07-09 20:13:45 -07004547 /* This code also provides auto-scrolling when a cursor is moved using a
4548 * CursorController (insertion point or selection limits).
4549 * For selection, ensure start or end is visible depending on controller's state.
4550 */
4551 int curs = getSelectionEnd();
Gilles Debunnee587d832010-11-23 20:20:11 -08004552 // Do not create the controller if it is not already created.
Gilles Debunne2d373a12012-04-20 15:32:19 -07004553 if (mEditor != null && mEditor.mSelectionModifierCursorController != null &&
4554 mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07004555 curs = getSelectionStart();
Gilles Debunne05336272010-07-09 20:13:45 -07004556 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004557
4558 /*
4559 * TODO: This should really only keep the end in view if
4560 * it already was before the text changed. I'm not sure
4561 * of a good way to tell from here if it was.
4562 */
Gilles Debunne60e21862012-01-30 15:04:14 -08004563 if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004564 curs = mText.length();
4565 }
4566
4567 if (curs >= 0) {
4568 changed = bringPointIntoView(curs);
4569 }
4570 } else {
4571 changed = bringTextIntoView();
4572 }
4573
Gilles Debunne64e54a62010-09-07 19:07:17 -07004574 // This has to be checked here since:
4575 // - onFocusChanged cannot start it when focus is given to a view with selected text (after
4576 // a screen rotation) since layout is not yet initialized at that point.
Gilles Debunne2d373a12012-04-20 15:32:19 -07004577 if (mEditor != null && mEditor.mCreatedWithASelection) {
4578 mEditor.startSelectionActionMode();
4579 mEditor.mCreatedWithASelection = false;
Gilles Debunnec01f3fe2010-12-22 17:07:36 -08004580 }
4581
4582 // Phone specific code (there is no ExtractEditText on tablets).
4583 // ExtractEditText does not call onFocus when it is displayed, and mHasSelectionOnFocus can
4584 // not be set. Do the test here instead.
Gilles Debunned88876a2012-03-16 17:34:04 -07004585 if (this instanceof ExtractEditText && hasSelection() && mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004586 mEditor.startSelectionActionMode();
Gilles Debunnef788a9f2010-07-22 10:17:23 -07004587 }
Gilles Debunne64e54a62010-09-07 19:07:17 -07004588
Gilles Debunne2e37d622012-01-27 13:54:00 -08004589 getViewTreeObserver().removeOnPreDrawListener(this);
4590 mPreDrawRegistered = false;
4591
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004592 return !changed;
4593 }
4594
4595 @Override
4596 protected void onAttachedToWindow() {
4597 super.onAttachedToWindow();
4598
4599 mTemporaryDetach = false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07004600
Gilles Debunne2d373a12012-04-20 15:32:19 -07004601 if (mEditor != null) mEditor.onAttachedToWindow();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004602 }
4603
4604 @Override
4605 protected void onDetachedFromWindow() {
4606 super.onDetachedFromWindow();
4607
Gilles Debunne2e37d622012-01-27 13:54:00 -08004608 if (mPreDrawRegistered) {
4609 getViewTreeObserver().removeOnPreDrawListener(this);
4610 mPreDrawRegistered = false;
Gilles Debunne81f08082011-02-17 14:07:19 -08004611 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004612
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07004613 resetResolvedDrawables();
Gilles Debunne186aaf92011-09-16 14:26:12 -07004614
Gilles Debunne2d373a12012-04-20 15:32:19 -07004615 if (mEditor != null) mEditor.onDetachedFromWindow();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004616 }
4617
4618 @Override
Romain Guybb9908b2012-03-08 11:14:07 -08004619 public void onScreenStateChanged(int screenState) {
4620 super.onScreenStateChanged(screenState);
Gilles Debunne2d373a12012-04-20 15:32:19 -07004621 if (mEditor != null) mEditor.onScreenStateChanged(screenState);
Romain Guybb9908b2012-03-08 11:14:07 -08004622 }
4623
4624 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004625 protected boolean isPaddingOffsetRequired() {
Romain Guy076dc9f2009-06-24 17:17:51 -07004626 return mShadowRadius != 0 || mDrawables != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004627 }
4628
4629 @Override
4630 protected int getLeftPaddingOffset() {
Romain Guy076dc9f2009-06-24 17:17:51 -07004631 return getCompoundPaddingLeft() - mPaddingLeft +
4632 (int) Math.min(0, mShadowDx - mShadowRadius);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004633 }
4634
4635 @Override
4636 protected int getTopPaddingOffset() {
4637 return (int) Math.min(0, mShadowDy - mShadowRadius);
4638 }
4639
4640 @Override
4641 protected int getBottomPaddingOffset() {
4642 return (int) Math.max(0, mShadowDy + mShadowRadius);
4643 }
4644
4645 @Override
4646 protected int getRightPaddingOffset() {
Romain Guy076dc9f2009-06-24 17:17:51 -07004647 return -(getCompoundPaddingRight() - mPaddingRight) +
4648 (int) Math.max(0, mShadowDx + mShadowRadius);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004649 }
4650
4651 @Override
4652 protected boolean verifyDrawable(Drawable who) {
4653 final boolean verified = super.verifyDrawable(who);
4654 if (!verified && mDrawables != null) {
4655 return who == mDrawables.mDrawableLeft || who == mDrawables.mDrawableTop ||
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07004656 who == mDrawables.mDrawableRight || who == mDrawables.mDrawableBottom ||
4657 who == mDrawables.mDrawableStart || who == mDrawables.mDrawableEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004658 }
4659 return verified;
4660 }
4661
4662 @Override
Dianne Hackborne2136772010-11-04 15:08:59 -07004663 public void jumpDrawablesToCurrentState() {
4664 super.jumpDrawablesToCurrentState();
4665 if (mDrawables != null) {
4666 if (mDrawables.mDrawableLeft != null) {
4667 mDrawables.mDrawableLeft.jumpToCurrentState();
4668 }
4669 if (mDrawables.mDrawableTop != null) {
4670 mDrawables.mDrawableTop.jumpToCurrentState();
4671 }
4672 if (mDrawables.mDrawableRight != null) {
4673 mDrawables.mDrawableRight.jumpToCurrentState();
4674 }
4675 if (mDrawables.mDrawableBottom != null) {
4676 mDrawables.mDrawableBottom.jumpToCurrentState();
4677 }
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07004678 if (mDrawables.mDrawableStart != null) {
4679 mDrawables.mDrawableStart.jumpToCurrentState();
4680 }
4681 if (mDrawables.mDrawableEnd != null) {
4682 mDrawables.mDrawableEnd.jumpToCurrentState();
4683 }
Dianne Hackborne2136772010-11-04 15:08:59 -07004684 }
4685 }
4686
4687 @Override
Romain Guy3c77d392009-05-20 11:26:50 -07004688 public void invalidateDrawable(Drawable drawable) {
4689 if (verifyDrawable(drawable)) {
4690 final Rect dirty = drawable.getBounds();
4691 int scrollX = mScrollX;
4692 int scrollY = mScrollY;
4693
4694 // IMPORTANT: The coordinates below are based on the coordinates computed
4695 // for each compound drawable in onDraw(). Make sure to update each section
4696 // accordingly.
4697 final TextView.Drawables drawables = mDrawables;
Romain Guya6cd4e02009-05-20 15:09:21 -07004698 if (drawables != null) {
4699 if (drawable == drawables.mDrawableLeft) {
4700 final int compoundPaddingTop = getCompoundPaddingTop();
4701 final int compoundPaddingBottom = getCompoundPaddingBottom();
4702 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
Romain Guy3c77d392009-05-20 11:26:50 -07004703
Romain Guya6cd4e02009-05-20 15:09:21 -07004704 scrollX += mPaddingLeft;
4705 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
4706 } else if (drawable == drawables.mDrawableRight) {
4707 final int compoundPaddingTop = getCompoundPaddingTop();
4708 final int compoundPaddingBottom = getCompoundPaddingBottom();
4709 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
Romain Guy3c77d392009-05-20 11:26:50 -07004710
Romain Guya6cd4e02009-05-20 15:09:21 -07004711 scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
4712 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
4713 } else if (drawable == drawables.mDrawableTop) {
4714 final int compoundPaddingLeft = getCompoundPaddingLeft();
4715 final int compoundPaddingRight = getCompoundPaddingRight();
4716 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
Romain Guy3c77d392009-05-20 11:26:50 -07004717
Romain Guya6cd4e02009-05-20 15:09:21 -07004718 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
4719 scrollY += mPaddingTop;
4720 } else if (drawable == drawables.mDrawableBottom) {
4721 final int compoundPaddingLeft = getCompoundPaddingLeft();
4722 final int compoundPaddingRight = getCompoundPaddingRight();
4723 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
Romain Guy3c77d392009-05-20 11:26:50 -07004724
Romain Guya6cd4e02009-05-20 15:09:21 -07004725 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
4726 scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
4727 }
Romain Guy3c77d392009-05-20 11:26:50 -07004728 }
4729
4730 invalidate(dirty.left + scrollX, dirty.top + scrollY,
4731 dirty.right + scrollX, dirty.bottom + scrollY);
4732 }
4733 }
4734
4735 @Override
Chet Haasedb8c9a62012-03-21 18:54:18 -07004736 public boolean hasOverlappingRendering() {
Chet Haase1271e2c2012-04-20 09:54:27 -07004737 return (getBackground() != null || mText instanceof Spannable || hasSelection());
Chet Haasedb8c9a62012-03-21 18:54:18 -07004738 }
4739
Gilles Debunne86b9c782010-11-11 10:43:48 -08004740 /**
4741 * When a TextView is used to display a useful piece of information to the user (such as a
4742 * contact's address), it should be made selectable, so that the user can select and copy this
4743 * content.
4744 *
4745 * Use {@link #setTextIsSelectable(boolean)} or the
4746 * {@link android.R.styleable#TextView_textIsSelectable} XML attribute to make this TextView
Gilles Debunnee12f9992010-12-17 11:04:55 -08004747 * selectable (text is not selectable by default).
Gilles Debunne6f100f32010-12-13 18:04:20 -08004748 *
Gilles Debunnebb588da2011-07-11 18:26:19 -07004749 * Note that this method simply returns the state of this flag. Although this flag has to be set
4750 * in order to select text in non-editable TextView, the content of an {@link EditText} can
4751 * always be selected, independently of the value of this flag.
Gilles Debunne86b9c782010-11-11 10:43:48 -08004752 *
4753 * @return True if the text displayed in this TextView can be selected by the user.
4754 *
4755 * @attr ref android.R.styleable#TextView_textIsSelectable
4756 */
4757 public boolean isTextSelectable() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004758 return mEditor == null ? false : mEditor.mTextIsSelectable;
Gilles Debunne86b9c782010-11-11 10:43:48 -08004759 }
4760
4761 /**
4762 * Sets whether or not (default) the content of this view is selectable by the user.
Gilles Debunne60e21862012-01-30 15:04:14 -08004763 *
Gilles Debunnee12f9992010-12-17 11:04:55 -08004764 * Note that this methods affect the {@link #setFocusable(boolean)},
Gilles Debunnecbcb3452010-12-17 15:31:02 -08004765 * {@link #setFocusableInTouchMode(boolean)} {@link #setClickable(boolean)} and
4766 * {@link #setLongClickable(boolean)} states and you may want to restore these if they were
4767 * customized.
Gilles Debunne86b9c782010-11-11 10:43:48 -08004768 *
4769 * See {@link #isTextSelectable} for details.
4770 *
4771 * @param selectable Whether or not the content of this TextView should be selectable.
4772 */
4773 public void setTextIsSelectable(boolean selectable) {
Gilles Debunne60e21862012-01-30 15:04:14 -08004774 if (!selectable && mEditor == null) return; // false is default value with no edit data
Gilles Debunne86b9c782010-11-11 10:43:48 -08004775
Gilles Debunne5fae9962012-05-08 14:53:20 -07004776 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004777 if (mEditor.mTextIsSelectable == selectable) return;
Gilles Debunne86b9c782010-11-11 10:43:48 -08004778
Gilles Debunne2d373a12012-04-20 15:32:19 -07004779 mEditor.mTextIsSelectable = selectable;
Gilles Debunnecbcb3452010-12-17 15:31:02 -08004780 setFocusableInTouchMode(selectable);
Gilles Debunne86b9c782010-11-11 10:43:48 -08004781 setFocusable(selectable);
4782 setClickable(selectable);
4783 setLongClickable(selectable);
4784
Gilles Debunne60e21862012-01-30 15:04:14 -08004785 // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
Gilles Debunne86b9c782010-11-11 10:43:48 -08004786
4787 setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
Gilles Debunne857c3412012-06-07 10:50:58 -07004788 setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
Gilles Debunne86b9c782010-11-11 10:43:48 -08004789
4790 // Called by setText above, but safer in case of future code changes
Gilles Debunne2d373a12012-04-20 15:32:19 -07004791 mEditor.prepareCursorControllers();
Gilles Debunne86b9c782010-11-11 10:43:48 -08004792 }
4793
4794 @Override
4795 protected int[] onCreateDrawableState(int extraSpace) {
Gilles Debunnefb817032011-01-13 13:52:49 -08004796 final int[] drawableState;
Gilles Debunneda0a3f02010-12-22 10:07:15 -08004797
Gilles Debunnefb817032011-01-13 13:52:49 -08004798 if (mSingleLine) {
4799 drawableState = super.onCreateDrawableState(extraSpace);
4800 } else {
4801 drawableState = super.onCreateDrawableState(extraSpace + 1);
Gilles Debunneda0a3f02010-12-22 10:07:15 -08004802 mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
4803 }
4804
Gilles Debunne60e21862012-01-30 15:04:14 -08004805 if (isTextSelectable()) {
Gilles Debunne86b9c782010-11-11 10:43:48 -08004806 // Disable pressed state, which was introduced when TextView was made clickable.
4807 // Prevents text color change.
4808 // setClickable(false) would have a similar effect, but it also disables focus changes
4809 // and long press actions, which are both needed by text selection.
4810 final int length = drawableState.length;
4811 for (int i = 0; i < length; i++) {
4812 if (drawableState[i] == R.attr.state_pressed) {
4813 final int[] nonPressedState = new int[length - 1];
4814 System.arraycopy(drawableState, 0, nonPressedState, 0, i);
4815 System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
4816 return nonPressedState;
4817 }
4818 }
4819 }
Gilles Debunneda0a3f02010-12-22 10:07:15 -08004820
Gilles Debunne86b9c782010-11-11 10:43:48 -08004821 return drawableState;
4822 }
4823
Gilles Debunne83051b82012-02-24 20:01:13 -08004824 private Path getUpdatedHighlightPath() {
4825 Path highlight = null;
4826 Paint highlightPaint = mHighlightPaint;
4827
4828 final int selStart = getSelectionStart();
4829 final int selEnd = getSelectionEnd();
4830 if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
4831 if (selStart == selEnd) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004832 if (mEditor != null && mEditor.isCursorVisible() &&
4833 (SystemClock.uptimeMillis() - mEditor.mShowCursor) %
Gilles Debunned88876a2012-03-16 17:34:04 -07004834 (2 * Editor.BLINK) < Editor.BLINK) {
Gilles Debunne83051b82012-02-24 20:01:13 -08004835 if (mHighlightPathBogus) {
4836 if (mHighlightPath == null) mHighlightPath = new Path();
4837 mHighlightPath.reset();
4838 mLayout.getCursorPath(selStart, mHighlightPath, mText);
Gilles Debunne2d373a12012-04-20 15:32:19 -07004839 mEditor.updateCursorsPositions();
Gilles Debunne83051b82012-02-24 20:01:13 -08004840 mHighlightPathBogus = false;
4841 }
4842
4843 // XXX should pass to skin instead of drawing directly
4844 highlightPaint.setColor(mCurTextColor);
Gilles Debunne83051b82012-02-24 20:01:13 -08004845 highlightPaint.setStyle(Paint.Style.STROKE);
4846 highlight = mHighlightPath;
4847 }
4848 } else {
4849 if (mHighlightPathBogus) {
4850 if (mHighlightPath == null) mHighlightPath = new Path();
4851 mHighlightPath.reset();
4852 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
4853 mHighlightPathBogus = false;
4854 }
4855
4856 // XXX should pass to skin instead of drawing directly
4857 highlightPaint.setColor(mHighlightColor);
Gilles Debunne83051b82012-02-24 20:01:13 -08004858 highlightPaint.setStyle(Paint.Style.FILL);
4859
4860 highlight = mHighlightPath;
4861 }
4862 }
4863 return highlight;
4864 }
4865
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -08004866 /**
4867 * @hide
4868 */
4869 public int getHorizontalOffsetForDrawables() {
4870 return 0;
4871 }
4872
Romain Guyc4d8eb62010-08-18 20:48:33 -07004873 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004874 protected void onDraw(Canvas canvas) {
Romain Guy986003d2009-03-25 17:42:35 -07004875 restartMarqueeIfNeeded();
4876
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004877 // Draw the background for this view
4878 super.onDraw(canvas);
4879
4880 final int compoundPaddingLeft = getCompoundPaddingLeft();
4881 final int compoundPaddingTop = getCompoundPaddingTop();
4882 final int compoundPaddingRight = getCompoundPaddingRight();
4883 final int compoundPaddingBottom = getCompoundPaddingBottom();
4884 final int scrollX = mScrollX;
4885 final int scrollY = mScrollY;
4886 final int right = mRight;
4887 final int left = mLeft;
4888 final int bottom = mBottom;
4889 final int top = mTop;
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -08004890 final boolean isLayoutRtl = isLayoutRtl();
4891 final int offset = getHorizontalOffsetForDrawables();
4892 final int leftOffset = isLayoutRtl ? 0 : offset;
4893 final int rightOffset = isLayoutRtl ? offset : 0 ;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004894
4895 final Drawables dr = mDrawables;
4896 if (dr != null) {
4897 /*
4898 * Compound, not extended, because the icon is not clipped
4899 * if the text height is smaller.
4900 */
4901
4902 int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
4903 int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
4904
Romain Guy3c77d392009-05-20 11:26:50 -07004905 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
4906 // Make sure to update invalidateDrawable() when changing this code.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004907 if (dr.mDrawableLeft != null) {
4908 canvas.save();
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -08004909 canvas.translate(scrollX + mPaddingLeft + leftOffset,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004910 scrollY + compoundPaddingTop +
4911 (vspace - dr.mDrawableHeightLeft) / 2);
4912 dr.mDrawableLeft.draw(canvas);
4913 canvas.restore();
4914 }
4915
Romain Guy3c77d392009-05-20 11:26:50 -07004916 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
4917 // Make sure to update invalidateDrawable() when changing this code.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004918 if (dr.mDrawableRight != null) {
4919 canvas.save();
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -08004920 canvas.translate(scrollX + right - left - mPaddingRight
4921 - dr.mDrawableSizeRight - rightOffset,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004922 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
4923 dr.mDrawableRight.draw(canvas);
4924 canvas.restore();
4925 }
4926
Romain Guy3c77d392009-05-20 11:26:50 -07004927 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
4928 // Make sure to update invalidateDrawable() when changing this code.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004929 if (dr.mDrawableTop != null) {
4930 canvas.save();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004931 canvas.translate(scrollX + compoundPaddingLeft +
4932 (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004933 dr.mDrawableTop.draw(canvas);
4934 canvas.restore();
4935 }
4936
Romain Guy3c77d392009-05-20 11:26:50 -07004937 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
4938 // Make sure to update invalidateDrawable() when changing this code.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004939 if (dr.mDrawableBottom != null) {
4940 canvas.save();
4941 canvas.translate(scrollX + compoundPaddingLeft +
4942 (hspace - dr.mDrawableWidthBottom) / 2,
4943 scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
4944 dr.mDrawableBottom.draw(canvas);
4945 canvas.restore();
4946 }
4947 }
4948
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004949 int color = mCurTextColor;
4950
4951 if (mLayout == null) {
4952 assumeLayout();
4953 }
4954
4955 Layout layout = mLayout;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004956
4957 if (mHint != null && mText.length() == 0) {
4958 if (mHintTextColor != null) {
4959 color = mCurHintTextColor;
4960 }
4961
4962 layout = mHintLayout;
4963 }
4964
4965 mTextPaint.setColor(color);
4966 mTextPaint.drawableState = getDrawableState();
4967
4968 canvas.save();
4969 /* Would be faster if we didn't have to do this. Can we chop the
4970 (displayable) text so that we don't need to do this ever?
4971 */
4972
4973 int extendedPaddingTop = getExtendedPaddingTop();
4974 int extendedPaddingBottom = getExtendedPaddingBottom();
4975
Fabrice Di Meglio132bda12012-02-07 17:02:00 -08004976 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
4977 final int maxScrollY = mLayout.getHeight() - vspace;
4978
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004979 float clipLeft = compoundPaddingLeft + scrollX;
Fabrice Di Meglio132bda12012-02-07 17:02:00 -08004980 float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004981 float clipRight = right - left - compoundPaddingRight + scrollX;
Fabrice Di Meglio132bda12012-02-07 17:02:00 -08004982 float clipBottom = bottom - top + scrollY -
4983 ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004984
4985 if (mShadowRadius != 0) {
4986 clipLeft += Math.min(0, mShadowDx - mShadowRadius);
4987 clipRight += Math.max(0, mShadowDx + mShadowRadius);
4988
4989 clipTop += Math.min(0, mShadowDy - mShadowRadius);
4990 clipBottom += Math.max(0, mShadowDy + mShadowRadius);
4991 }
4992
4993 canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
4994
4995 int voffsetText = 0;
4996 int voffsetCursor = 0;
4997
4998 // translate in by our padding
Gilles Debunne60e21862012-01-30 15:04:14 -08004999 /* shortcircuit calling getVerticaOffset() */
5000 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5001 voffsetText = getVerticalOffset(false);
5002 voffsetCursor = getVerticalOffset(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005003 }
Gilles Debunne60e21862012-01-30 15:04:14 -08005004 canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005005
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07005006 final int layoutDirection = getLayoutDirection();
Fabrice Di Meglioc0053222011-06-13 12:16:51 -07005007 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
Adam Powell282e3772011-08-30 16:51:11 -07005008 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
5009 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005010 if (!mSingleLine && getLineCount() == 1 && canMarquee() &&
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07005011 (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07005012 final int width = mRight - mLeft;
5013 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
5014 final float dx = mLayout.getLineRight(0) - (width - padding);
5015 canvas.translate(isLayoutRtl ? -dx : +dx, 0.0f);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005016 }
5017
5018 if (mMarquee != null && mMarquee.isRunning()) {
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07005019 final float dx = -mMarquee.getScroll();
5020 canvas.translate(isLayoutRtl ? -dx : +dx, 0.0f);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005021 }
5022 }
5023
Gilles Debunne12d91ce2010-12-10 11:36:29 -08005024 final int cursorOffsetVertical = voffsetCursor - voffsetText;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005025
Gilles Debunne83051b82012-02-24 20:01:13 -08005026 Path highlight = getUpdatedHighlightPath();
Gilles Debunne60e21862012-01-30 15:04:14 -08005027 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005028 mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
Gilles Debunneb35ab7b2011-12-05 15:54:00 -08005029 } else {
Gilles Debunne83051b82012-02-24 20:01:13 -08005030 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
Gilles Debunned88876a2012-03-16 17:34:04 -07005031 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005032
Gilles Debunned88876a2012-03-16 17:34:04 -07005033 if (mMarquee != null && mMarquee.shouldDrawGhost()) {
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07005034 final int dx = (int) mMarquee.getGhostOffset();
5035 canvas.translate(isLayoutRtl ? -dx : dx, 0.0f);
Gilles Debunned88876a2012-03-16 17:34:04 -07005036 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
Romain Guyc2303192009-04-03 17:37:18 -07005037 }
5038
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005039 canvas.restore();
Leon Scroggins56426252010-11-01 15:45:37 -04005040 }
5041
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005042 @Override
5043 public void getFocusedRect(Rect r) {
5044 if (mLayout == null) {
5045 super.getFocusedRect(r);
5046 return;
5047 }
5048
Dianne Hackborn70a3f672011-08-08 14:32:41 -07005049 int selEnd = getSelectionEnd();
5050 if (selEnd < 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005051 super.getFocusedRect(r);
5052 return;
5053 }
5054
Dianne Hackborn70a3f672011-08-08 14:32:41 -07005055 int selStart = getSelectionStart();
5056 if (selStart < 0 || selStart >= selEnd) {
5057 int line = mLayout.getLineForOffset(selEnd);
5058 r.top = mLayout.getLineTop(line);
5059 r.bottom = mLayout.getLineBottom(line);
5060 r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
5061 r.right = r.left + 4;
5062 } else {
5063 int lineStart = mLayout.getLineForOffset(selStart);
5064 int lineEnd = mLayout.getLineForOffset(selEnd);
5065 r.top = mLayout.getLineTop(lineStart);
5066 r.bottom = mLayout.getLineBottom(lineEnd);
5067 if (lineStart == lineEnd) {
5068 r.left = (int) mLayout.getPrimaryHorizontal(selStart);
5069 r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
5070 } else {
Gilles Debunne60e21862012-01-30 15:04:14 -08005071 // Selection extends across multiple lines -- make the focused
5072 // rect cover the entire width.
Gilles Debunne83051b82012-02-24 20:01:13 -08005073 if (mHighlightPathBogus) {
5074 if (mHighlightPath == null) mHighlightPath = new Path();
5075 mHighlightPath.reset();
5076 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
5077 mHighlightPathBogus = false;
5078 }
5079 synchronized (TEMP_RECTF) {
5080 mHighlightPath.computeBounds(TEMP_RECTF, true);
5081 r.left = (int)TEMP_RECTF.left-1;
5082 r.right = (int)TEMP_RECTF.right+1;
Dianne Hackborn70a3f672011-08-08 14:32:41 -07005083 }
5084 }
5085 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005086
5087 // Adjust for padding and gravity.
5088 int paddingLeft = getCompoundPaddingLeft();
5089 int paddingTop = getExtendedPaddingTop();
5090 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5091 paddingTop += getVerticalOffset(false);
5092 }
5093 r.offset(paddingLeft, paddingTop);
Gilles Debunne322044a2012-02-22 12:01:40 -08005094 int paddingBottom = getExtendedPaddingBottom();
5095 r.bottom += paddingBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005096 }
5097
5098 /**
5099 * Return the number of lines of text, or 0 if the internal Layout has not
5100 * been built.
5101 */
5102 public int getLineCount() {
5103 return mLayout != null ? mLayout.getLineCount() : 0;
5104 }
5105
5106 /**
5107 * Return the baseline for the specified line (0...getLineCount() - 1)
5108 * If bounds is not null, return the top, left, right, bottom extents
5109 * of the specified line in it. If the internal Layout has not been built,
5110 * return 0 and set bounds to (0, 0, 0, 0)
5111 * @param line which line to examine (0..getLineCount() - 1)
5112 * @param bounds Optional. If not null, it returns the extent of the line
5113 * @return the Y-coordinate of the baseline
5114 */
5115 public int getLineBounds(int line, Rect bounds) {
5116 if (mLayout == null) {
5117 if (bounds != null) {
5118 bounds.set(0, 0, 0, 0);
5119 }
5120 return 0;
5121 }
5122 else {
5123 int baseline = mLayout.getLineBounds(line, bounds);
5124
5125 int voffset = getExtendedPaddingTop();
5126 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5127 voffset += getVerticalOffset(true);
5128 }
5129 if (bounds != null) {
5130 bounds.offset(getCompoundPaddingLeft(), voffset);
5131 }
5132 return baseline + voffset;
5133 }
5134 }
5135
5136 @Override
5137 public int getBaseline() {
5138 if (mLayout == null) {
5139 return super.getBaseline();
5140 }
5141
5142 int voffset = 0;
5143 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5144 voffset = getVerticalOffset(true);
5145 }
5146
5147 return getExtendedPaddingTop() + voffset + mLayout.getLineBaseline(0);
5148 }
5149
Romain Guyf2fc4602011-07-19 15:20:03 -07005150 /**
5151 * @hide
Romain Guyf2fc4602011-07-19 15:20:03 -07005152 */
5153 @Override
5154 protected int getFadeTop(boolean offsetRequired) {
Romain Guy59f13c7d2011-07-19 18:35:33 -07005155 if (mLayout == null) return 0;
5156
Romain Guyf2fc4602011-07-19 15:20:03 -07005157 int voffset = 0;
5158 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5159 voffset = getVerticalOffset(true);
5160 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005161
Romain Guyf2fc4602011-07-19 15:20:03 -07005162 if (offsetRequired) voffset += getTopPaddingOffset();
5163
5164 return getExtendedPaddingTop() + voffset;
5165 }
5166
5167 /**
5168 * @hide
Romain Guyf2fc4602011-07-19 15:20:03 -07005169 */
Gilles Debunne3784a7f2011-07-15 13:49:38 -07005170 @Override
Romain Guyf2fc4602011-07-19 15:20:03 -07005171 protected int getFadeHeight(boolean offsetRequired) {
5172 return mLayout != null ? mLayout.getHeight() : 0;
5173 }
5174
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005175 @Override
Gilles Debunne4a7199a2011-07-11 14:58:27 -07005176 public boolean onKeyPreIme(int keyCode, KeyEvent event) {
5177 if (keyCode == KeyEvent.KEYCODE_BACK) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005178 boolean isInSelectionMode = mEditor != null && mEditor.mSelectionActionMode != null;
Gilles Debunne4a7199a2011-07-11 14:58:27 -07005179
Gilles Debunne28294cc2011-08-24 12:02:05 -07005180 if (isInSelectionMode) {
Gilles Debunne4a7199a2011-07-11 14:58:27 -07005181 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
5182 KeyEvent.DispatcherState state = getKeyDispatcherState();
5183 if (state != null) {
5184 state.startTracking(event, this);
5185 }
5186 return true;
5187 } else if (event.getAction() == KeyEvent.ACTION_UP) {
5188 KeyEvent.DispatcherState state = getKeyDispatcherState();
5189 if (state != null) {
5190 state.handleUpEvent(event);
5191 }
5192 if (event.isTracking() && !event.isCanceled()) {
Gilles Debunne14568c32012-01-13 15:26:05 -08005193 stopSelectionActionMode();
5194 return true;
Gilles Debunne4a7199a2011-07-11 14:58:27 -07005195 }
5196 }
5197 }
5198 }
5199 return super.onKeyPreIme(keyCode, event);
5200 }
5201
5202 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005203 public boolean onKeyDown(int keyCode, KeyEvent event) {
5204 int which = doKeyDown(keyCode, event, null);
5205 if (which == 0) {
5206 // Go through default dispatching.
5207 return super.onKeyDown(keyCode, event);
5208 }
5209
5210 return true;
5211 }
5212
5213 @Override
5214 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005215 KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005216
5217 int which = doKeyDown(keyCode, down, event);
5218 if (which == 0) {
5219 // Go through default dispatching.
5220 return super.onKeyMultiple(keyCode, repeatCount, event);
5221 }
5222 if (which == -1) {
5223 // Consumed the whole thing.
5224 return true;
5225 }
5226
5227 repeatCount--;
Gilles Debunne2d373a12012-04-20 15:32:19 -07005228
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005229 // We are going to dispatch the remaining events to either the input
5230 // or movement method. To do this, we will just send a repeated stream
5231 // of down and up events until we have done the complete repeatCount.
5232 // It would be nice if those interfaces had an onKeyMultiple() method,
5233 // but adding that is a more complicated change.
The Android Open Source Project10592532009-03-18 17:39:46 -07005234 KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005235 if (which == 1) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005236 // mEditor and mEditor.mInput are not null from doKeyDown
5237 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005238 while (--repeatCount > 0) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005239 mEditor.mKeyListener.onKeyDown(this, (Editable)mText, keyCode, down);
5240 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005241 }
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08005242 hideErrorIfUnchanged();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005243
5244 } else if (which == 2) {
Gilles Debunne60e21862012-01-30 15:04:14 -08005245 // mMovement is not null from doKeyDown
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005246 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
5247 while (--repeatCount > 0) {
5248 mMovement.onKeyDown(this, (Spannable)mText, keyCode, down);
5249 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
5250 }
5251 }
5252
5253 return true;
5254 }
5255
5256 /**
5257 * Returns true if pressing ENTER in this field advances focus instead
5258 * of inserting the character. This is true mostly in single-line fields,
5259 * but also in mail addresses and subjects which will display on multiple
5260 * lines but where it doesn't make sense to insert newlines.
5261 */
The Android Open Source Project4df24232009-03-05 14:34:35 -08005262 private boolean shouldAdvanceFocusOnEnter() {
Gilles Debunne60e21862012-01-30 15:04:14 -08005263 if (getKeyListener() == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005264 return false;
5265 }
5266
5267 if (mSingleLine) {
5268 return true;
5269 }
5270
Gilles Debunne2d373a12012-04-20 15:32:19 -07005271 if (mEditor != null &&
5272 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
5273 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005274 if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
5275 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005276 return true;
5277 }
5278 }
5279
5280 return false;
5281 }
5282
Jeff Brown4e6319b2010-12-13 10:36:51 -08005283 /**
5284 * Returns true if pressing TAB in this field advances focus instead
5285 * of inserting the character. Insert tabs only in multi-line editors.
5286 */
5287 private boolean shouldAdvanceFocusOnTab() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005288 if (getKeyListener() != null && !mSingleLine && mEditor != null &&
5289 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
5290 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
5291 if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
5292 || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
5293 return false;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005294 }
5295 }
5296 return true;
5297 }
5298
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005299 private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
5300 if (!isEnabled()) {
5301 return 0;
5302 }
5303
5304 switch (keyCode) {
5305 case KeyEvent.KEYCODE_ENTER:
Jeff Brown4e6319b2010-12-13 10:36:51 -08005306 if (event.hasNoModifiers()) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005307 // When mInputContentType is set, we know that we are
5308 // running in a "modern" cupcake environment, so don't need
5309 // to worry about the application trying to capture
5310 // enter key events.
Gilles Debunne2d373a12012-04-20 15:32:19 -07005311 if (mEditor != null && mEditor.mInputContentType != null) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005312 // If there is an action listener, given them a
5313 // chance to consume the event.
Gilles Debunne2d373a12012-04-20 15:32:19 -07005314 if (mEditor.mInputContentType.onEditorActionListener != null &&
5315 mEditor.mInputContentType.onEditorActionListener.onEditorAction(
The Android Open Source Project10592532009-03-18 17:39:46 -07005316 this, EditorInfo.IME_NULL, event)) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005317 mEditor.mInputContentType.enterDown = true;
The Android Open Source Project10592532009-03-18 17:39:46 -07005318 // We are consuming the enter key for them.
5319 return -1;
5320 }
5321 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08005322
The Android Open Source Project10592532009-03-18 17:39:46 -07005323 // If our editor should move focus when enter is pressed, or
5324 // this is a generated event from an IME action button, then
5325 // don't let it be inserted into the text.
Jeff Brown4e6319b2010-12-13 10:36:51 -08005326 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
The Android Open Source Project10592532009-03-18 17:39:46 -07005327 || shouldAdvanceFocusOnEnter()) {
Dianne Hackborn0500b3c2011-11-01 15:28:43 -07005328 if (hasOnClickListeners()) {
Leon Scroggins7014b122011-01-11 15:17:34 -05005329 return 0;
5330 }
The Android Open Source Project10592532009-03-18 17:39:46 -07005331 return -1;
5332 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005333 }
The Android Open Source Project10592532009-03-18 17:39:46 -07005334 break;
Gilles Debunne2d373a12012-04-20 15:32:19 -07005335
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005336 case KeyEvent.KEYCODE_DPAD_CENTER:
Jeff Brown4e6319b2010-12-13 10:36:51 -08005337 if (event.hasNoModifiers()) {
5338 if (shouldAdvanceFocusOnEnter()) {
5339 return 0;
5340 }
5341 }
5342 break;
5343
5344 case KeyEvent.KEYCODE_TAB:
5345 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
5346 if (shouldAdvanceFocusOnTab()) {
5347 return 0;
5348 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005349 }
Gilles Debunne64e54a62010-09-07 19:07:17 -07005350 break;
5351
5352 // Has to be done on key down (and not on key up) to correctly be intercepted.
5353 case KeyEvent.KEYCODE_BACK:
Gilles Debunne2d373a12012-04-20 15:32:19 -07005354 if (mEditor != null && mEditor.mSelectionActionMode != null) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07005355 stopSelectionActionMode();
5356 return -1;
5357 }
5358 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005359 }
5360
Gilles Debunne2d373a12012-04-20 15:32:19 -07005361 if (mEditor != null && mEditor.mKeyListener != null) {
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08005362 resetErrorChangedFlag();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005363
5364 boolean doDown = true;
5365 if (otherEvent != null) {
5366 try {
5367 beginBatchEdit();
Gilles Debunne2d373a12012-04-20 15:32:19 -07005368 final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText,
5369 otherEvent);
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08005370 hideErrorIfUnchanged();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005371 doDown = false;
5372 if (handled) {
5373 return -1;
5374 }
5375 } catch (AbstractMethodError e) {
5376 // onKeyOther was added after 1.0, so if it isn't
5377 // implemented we need to try to dispatch as a regular down.
5378 } finally {
5379 endBatchEdit();
5380 }
5381 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005382
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005383 if (doDown) {
5384 beginBatchEdit();
Gilles Debunne2d373a12012-04-20 15:32:19 -07005385 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText,
5386 keyCode, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005387 endBatchEdit();
Gilles Debunne12ab6452011-01-30 12:08:25 -08005388 hideErrorIfUnchanged();
5389 if (handled) return 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005390 }
5391 }
5392
5393 // bug 650865: sometimes we get a key event before a layout.
5394 // don't try to move around if we don't know the layout.
5395
5396 if (mMovement != null && mLayout != null) {
5397 boolean doDown = true;
5398 if (otherEvent != null) {
5399 try {
5400 boolean handled = mMovement.onKeyOther(this, (Spannable) mText,
5401 otherEvent);
5402 doDown = false;
5403 if (handled) {
5404 return -1;
5405 }
5406 } catch (AbstractMethodError e) {
5407 // onKeyOther was added after 1.0, so if it isn't
5408 // implemented we need to try to dispatch as a regular down.
5409 }
5410 }
5411 if (doDown) {
5412 if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event))
5413 return 2;
5414 }
5415 }
5416
5417 return 0;
5418 }
5419
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08005420 /**
5421 * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
5422 * can be recorded.
5423 * @hide
5424 */
5425 public void resetErrorChangedFlag() {
5426 /*
5427 * Keep track of what the error was before doing the input
5428 * so that if an input filter changed the error, we leave
5429 * that error showing. Otherwise, we take down whatever
5430 * error was showing when the user types something.
5431 */
Gilles Debunne2d373a12012-04-20 15:32:19 -07005432 if (mEditor != null) mEditor.mErrorWasChanged = false;
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08005433 }
5434
5435 /**
5436 * @hide
5437 */
5438 public void hideErrorIfUnchanged() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005439 if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) {
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08005440 setError(null, null);
5441 }
5442 }
5443
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005444 @Override
5445 public boolean onKeyUp(int keyCode, KeyEvent event) {
5446 if (!isEnabled()) {
5447 return super.onKeyUp(keyCode, event);
5448 }
5449
5450 switch (keyCode) {
5451 case KeyEvent.KEYCODE_DPAD_CENTER:
Jeff Brown4e6319b2010-12-13 10:36:51 -08005452 if (event.hasNoModifiers()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005453 /*
5454 * If there is a click listener, just call through to
5455 * super, which will invoke it.
5456 *
Jeff Brown4e6319b2010-12-13 10:36:51 -08005457 * If there isn't a click listener, try to show the soft
5458 * input method. (It will also
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005459 * call performClick(), but that won't do anything in
5460 * this case.)
5461 */
Gilles Debunne06a8e9b2011-12-08 10:39:39 -08005462 if (!hasOnClickListeners()) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08005463 if (mMovement != null && mText instanceof Editable
5464 && mLayout != null && onCheckIsTextEditor()) {
Gilles Debunne17d31de2011-01-27 11:02:18 -08005465 InputMethodManager imm = InputMethodManager.peekInstance();
satoka67a3cf2011-09-07 17:14:03 +09005466 viewClicked(imm);
Gilles Debunne3473b2b2012-04-20 16:21:10 -07005467 if (imm != null && getShowSoftInputOnFocus()) {
satok863fcd62011-06-21 17:38:02 +09005468 imm.showSoftInput(this, 0);
5469 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08005470 }
5471 }
5472 }
5473 return super.onKeyUp(keyCode, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005474
Jeff Brown4e6319b2010-12-13 10:36:51 -08005475 case KeyEvent.KEYCODE_ENTER:
Jeff Brown4e6319b2010-12-13 10:36:51 -08005476 if (event.hasNoModifiers()) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005477 if (mEditor != null && mEditor.mInputContentType != null
5478 && mEditor.mInputContentType.onEditorActionListener != null
5479 && mEditor.mInputContentType.enterDown) {
5480 mEditor.mInputContentType.enterDown = false;
5481 if (mEditor.mInputContentType.onEditorActionListener.onEditorAction(
Jeff Brown4e6319b2010-12-13 10:36:51 -08005482 this, EditorInfo.IME_NULL, event)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005483 return true;
5484 }
5485 }
5486
Jeff Brown4e6319b2010-12-13 10:36:51 -08005487 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
5488 || shouldAdvanceFocusOnEnter()) {
5489 /*
5490 * If there is a click listener, just call through to
5491 * super, which will invoke it.
5492 *
5493 * If there isn't a click listener, try to advance focus,
5494 * but still call through to super, which will reset the
5495 * pressed state and longpress state. (It will also
5496 * call performClick(), but that won't do anything in
5497 * this case.)
5498 */
Gilles Debunne06a8e9b2011-12-08 10:39:39 -08005499 if (!hasOnClickListeners()) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08005500 View v = focusSearch(FOCUS_DOWN);
5501
5502 if (v != null) {
5503 if (!v.requestFocus(FOCUS_DOWN)) {
5504 throw new IllegalStateException(
5505 "focus search returned a view " +
5506 "that wasn't able to take focus!");
5507 }
5508
5509 /*
5510 * Return true because we handled the key; super
5511 * will return false because there was no click
5512 * listener.
5513 */
5514 super.onKeyUp(keyCode, event);
5515 return true;
5516 } else if ((event.getFlags()
5517 & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
5518 // No target for next focus, but make sure the IME
5519 // if this came from it.
5520 InputMethodManager imm = InputMethodManager.peekInstance();
Gilles Debunne17d31de2011-01-27 11:02:18 -08005521 if (imm != null && imm.isActive(this)) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08005522 imm.hideSoftInputFromWindow(getWindowToken(), 0);
5523 }
5524 }
5525 }
5526 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005527 return super.onKeyUp(keyCode, event);
5528 }
Gilles Debunne64e54a62010-09-07 19:07:17 -07005529 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005530 }
5531
Gilles Debunne2d373a12012-04-20 15:32:19 -07005532 if (mEditor != null && mEditor.mKeyListener != null)
5533 if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event))
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005534 return true;
5535
5536 if (mMovement != null && mLayout != null)
5537 if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event))
5538 return true;
5539
5540 return super.onKeyUp(keyCode, event);
5541 }
5542
Gilles Debunnec1714022012-01-17 13:59:23 -08005543 @Override
5544 public boolean onCheckIsTextEditor() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005545 return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005546 }
Gilles Debunneb062e812011-09-27 14:58:37 -07005547
Gilles Debunnec1714022012-01-17 13:59:23 -08005548 @Override
5549 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
Janos Levai042856c2010-10-15 02:53:58 +03005550 if (onCheckIsTextEditor() && isEnabled()) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005551 mEditor.createInputMethodStateIfNeeded();
Gilles Debunne60e21862012-01-30 15:04:14 -08005552 outAttrs.inputType = getInputType();
Gilles Debunne2d373a12012-04-20 15:32:19 -07005553 if (mEditor.mInputContentType != null) {
5554 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
5555 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
5556 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
5557 outAttrs.actionId = mEditor.mInputContentType.imeActionId;
5558 outAttrs.extras = mEditor.mInputContentType.extras;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005559 } else {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005560 outAttrs.imeOptions = EditorInfo.IME_NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005561 }
Dianne Hackborndea3ef72010-10-28 14:24:22 -07005562 if (focusSearch(FOCUS_DOWN) != null) {
5563 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
5564 }
5565 if (focusSearch(FOCUS_UP) != null) {
5566 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
5567 }
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005568 if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION)
5569 == EditorInfo.IME_ACTION_UNSPECIFIED) {
Dianne Hackborndea3ef72010-10-28 14:24:22 -07005570 if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005571 // An action has not been set, but the enter key will move to
5572 // the next focus, so set the action to that.
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005573 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
The Android Open Source Project4df24232009-03-05 14:34:35 -08005574 } else {
5575 // An action has not been set, and there is no focus to move
5576 // to, so let's just supply a "done" action.
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005577 outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
The Android Open Source Project4df24232009-03-05 14:34:35 -08005578 }
5579 if (!shouldAdvanceFocusOnEnter()) {
5580 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005581 }
5582 }
Gilles Debunne91a08cf2010-11-08 17:34:49 -08005583 if (isMultilineInputType(outAttrs.inputType)) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005584 // Multi-line text editors should always show an enter key.
5585 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
5586 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005587 outAttrs.hintText = mHint;
5588 if (mText instanceof Editable) {
5589 InputConnection ic = new EditableInputConnection(this);
Gilles Debunne05336272010-07-09 20:13:45 -07005590 outAttrs.initialSelStart = getSelectionStart();
5591 outAttrs.initialSelEnd = getSelectionEnd();
Gilles Debunne60e21862012-01-30 15:04:14 -08005592 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005593 return ic;
5594 }
5595 }
5596 return null;
5597 }
5598
5599 /**
5600 * If this TextView contains editable content, extract a portion of it
5601 * based on the information in <var>request</var> in to <var>outText</var>.
5602 * @return Returns true if the text was successfully extracted, else false.
5603 */
Gilles Debunned88876a2012-03-16 17:34:04 -07005604 public boolean extractText(ExtractedTextRequest request, ExtractedText outText) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07005605 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07005606 return mEditor.extractText(request, outText);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005607 }
Viktor Yakovel964be412010-02-17 08:35:57 +01005608
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005609 /**
5610 * This is used to remove all style-impacting spans from text before new
5611 * extracted text is being replaced into it, so that we don't have any
5612 * lingering spans applied during the replace.
5613 */
5614 static void removeParcelableSpans(Spannable spannable, int start, int end) {
5615 Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
5616 int i = spans.length;
5617 while (i > 0) {
5618 i--;
5619 spannable.removeSpan(spans[i]);
5620 }
5621 }
Gilles Debunned88876a2012-03-16 17:34:04 -07005622
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005623 /**
5624 * Apply to this text view the given extracted text, as previously
5625 * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
5626 */
5627 public void setExtractedText(ExtractedText text) {
5628 Editable content = getEditableText();
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005629 if (text.text != null) {
5630 if (content == null) {
5631 setText(text.text, TextView.BufferType.EDITABLE);
5632 } else if (text.partialStartOffset < 0) {
5633 removeParcelableSpans(content, 0, content.length());
5634 content.replace(0, content.length(), text.text);
5635 } else {
5636 final int N = content.length();
5637 int start = text.partialStartOffset;
5638 if (start > N) start = N;
5639 int end = text.partialEndOffset;
5640 if (end > N) end = N;
5641 removeParcelableSpans(content, start, end);
5642 content.replace(start, end, text.text);
5643 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005644 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005645
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005646 // Now set the selection position... make sure it is in range, to
5647 // avoid crashes. If this is a partial update, it is possible that
5648 // the underlying text may have changed, causing us problems here.
5649 // Also we just don't want to trust clients to do the right thing.
5650 Spannable sp = (Spannable)getText();
5651 final int N = sp.length();
5652 int start = text.selectionStart;
5653 if (start < 0) start = 0;
5654 else if (start > N) start = N;
5655 int end = text.selectionEnd;
5656 if (end < 0) end = 0;
5657 else if (end > N) end = N;
5658 Selection.setSelection(sp, start, end);
Gilles Debunne2d373a12012-04-20 15:32:19 -07005659
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005660 // Finally, update the selection mode.
5661 if ((text.flags&ExtractedText.FLAG_SELECTING) != 0) {
5662 MetaKeyKeyListener.startSelecting(this, sp);
5663 } else {
5664 MetaKeyKeyListener.stopSelecting(this, sp);
5665 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005666 }
Gilles Debunne98fb9ed2011-09-07 17:15:41 -07005667
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005668 /**
5669 * @hide
5670 */
5671 public void setExtracting(ExtractedTextRequest req) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005672 if (mEditor.mInputMethodState != null) {
5673 mEditor.mInputMethodState.mExtractedTextRequest = req;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005674 }
Gilles Debunne98fb9ed2011-09-07 17:15:41 -07005675 // This would stop a possible selection mode, but no such mode is started in case
5676 // extracted mode will start. Some text is selected though, and will trigger an action mode
5677 // in the extracted view.
Gilles Debunne2d373a12012-04-20 15:32:19 -07005678 mEditor.hideControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005679 }
Gilles Debunne98fb9ed2011-09-07 17:15:41 -07005680
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005681 /**
5682 * Called by the framework in response to a text completion from
5683 * the current input method, provided by it calling
5684 * {@link InputConnection#commitCompletion
5685 * InputConnection.commitCompletion()}. The default implementation does
5686 * nothing; text views that are supporting auto-completion should override
5687 * this to do their desired behavior.
5688 *
5689 * @param text The auto complete text the user has selected.
5690 */
5691 public void onCommitCompletion(CompletionInfo text) {
Gilles Debunned40cc3c2011-03-11 16:59:32 -08005692 // intentionally empty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005693 }
5694
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08005695 /**
5696 * Called by the framework in response to a text auto-correction (such as fixing a typo using a
5697 * a dictionnary) from the current input method, provided by it calling
5698 * {@link InputConnection#commitCorrection} InputConnection.commitCorrection()}. The default
5699 * implementation flashes the background of the corrected word to provide feedback to the user.
5700 *
5701 * @param info The auto correct info about the text that was corrected.
5702 */
5703 public void onCommitCorrection(CorrectionInfo info) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005704 if (mEditor != null) mEditor.onCommitCorrection(info);
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08005705 }
5706
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005707 public void beginBatchEdit() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005708 if (mEditor != null) mEditor.beginBatchEdit();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005709 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005710
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005711 public void endBatchEdit() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005712 if (mEditor != null) mEditor.endBatchEdit();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005713 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005714
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005715 /**
5716 * Called by the framework in response to a request to begin a batch
5717 * of edit operations through a call to link {@link #beginBatchEdit()}.
5718 */
5719 public void onBeginBatchEdit() {
Gilles Debunned40cc3c2011-03-11 16:59:32 -08005720 // intentionally empty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005721 }
Gilles Debunne60e21862012-01-30 15:04:14 -08005722
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005723 /**
5724 * Called by the framework in response to a request to end a batch
5725 * of edit operations through a call to link {@link #endBatchEdit}.
5726 */
5727 public void onEndBatchEdit() {
Gilles Debunned40cc3c2011-03-11 16:59:32 -08005728 // intentionally empty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005729 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005730
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005731 /**
5732 * Called by the framework in response to a private command from the
5733 * current method, provided by it calling
5734 * {@link InputConnection#performPrivateCommand
5735 * InputConnection.performPrivateCommand()}.
5736 *
5737 * @param action The action name of the command.
5738 * @param data Any additional data for the command. This may be null.
5739 * @return Return true if you handled the command, else false.
5740 */
5741 public boolean onPrivateIMECommand(String action, Bundle data) {
5742 return false;
5743 }
5744
5745 private void nullLayouts() {
5746 if (mLayout instanceof BoringLayout && mSavedLayout == null) {
5747 mSavedLayout = (BoringLayout) mLayout;
5748 }
5749 if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
5750 mSavedHintLayout = (BoringLayout) mHintLayout;
5751 }
5752
Adam Powell282e3772011-08-30 16:51:11 -07005753 mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
Gilles Debunne77f18b02010-10-22 14:28:25 -07005754
Fabrice Di Megliod4c3b8e2011-11-09 18:04:07 -08005755 mBoring = mHintBoring = null;
5756
Gilles Debunne77f18b02010-10-22 14:28:25 -07005757 // Since it depends on the value of mLayout
Gilles Debunne2d373a12012-04-20 15:32:19 -07005758 if (mEditor != null) mEditor.prepareCursorControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005759 }
5760
5761 /**
5762 * Make a new Layout based on the already-measured size of the view,
5763 * on the assumption that it was measured correctly at some point.
5764 */
5765 private void assumeLayout() {
5766 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
5767
5768 if (width < 1) {
5769 width = 0;
5770 }
5771
5772 int physicalWidth = width;
5773
5774 if (mHorizontallyScrolling) {
Jeff Brown033a0012011-11-11 15:30:16 -08005775 width = VERY_WIDE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005776 }
5777
5778 makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
5779 physicalWidth, false);
5780 }
5781
Fabrice Di Meglio9da0f8a2012-03-13 19:37:57 -07005782 @Override
Fabrice Di Meglio343e1132012-09-28 18:01:17 -07005783 public void onRtlPropertiesChanged(int layoutDirection) {
Fabrice Di Meglio9da0f8a2012-03-13 19:37:57 -07005784 if (mLayoutAlignment != null) {
Fabrice Di Megliofa1babd2012-09-04 19:11:25 -07005785 if (mResolvedTextAlignment == TEXT_ALIGNMENT_VIEW_START ||
5786 mResolvedTextAlignment == TEXT_ALIGNMENT_VIEW_END) {
Fabrice Di Meglio9da0f8a2012-03-13 19:37:57 -07005787 mLayoutAlignment = null;
5788 }
5789 }
5790 }
5791
Doug Feltc0ccf0c2011-06-23 16:13:18 -07005792 private Layout.Alignment getLayoutAlignment() {
5793 if (mLayoutAlignment == null) {
Fabrice Di Meglio1a7d4872012-09-23 16:19:58 -07005794 mResolvedTextAlignment = getTextAlignment();
Fabrice Di Megliofa1babd2012-09-04 19:11:25 -07005795 switch (mResolvedTextAlignment) {
Fabrice Di Meglio9da0f8a2012-03-13 19:37:57 -07005796 case TEXT_ALIGNMENT_GRAVITY:
5797 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
5798 case Gravity.START:
5799 mLayoutAlignment = Layout.Alignment.ALIGN_NORMAL;
5800 break;
5801 case Gravity.END:
5802 mLayoutAlignment = Layout.Alignment.ALIGN_OPPOSITE;
5803 break;
5804 case Gravity.LEFT:
5805 mLayoutAlignment = Layout.Alignment.ALIGN_LEFT;
5806 break;
5807 case Gravity.RIGHT:
5808 mLayoutAlignment = Layout.Alignment.ALIGN_RIGHT;
5809 break;
5810 case Gravity.CENTER_HORIZONTAL:
5811 mLayoutAlignment = Layout.Alignment.ALIGN_CENTER;
5812 break;
5813 default:
5814 mLayoutAlignment = Layout.Alignment.ALIGN_NORMAL;
5815 break;
5816 }
5817 break;
5818 case TEXT_ALIGNMENT_TEXT_START:
Gilles Debunne978a8ff2012-02-22 14:27:12 -08005819 mLayoutAlignment = Layout.Alignment.ALIGN_NORMAL;
Doug Feltc0ccf0c2011-06-23 16:13:18 -07005820 break;
Fabrice Di Meglio9da0f8a2012-03-13 19:37:57 -07005821 case TEXT_ALIGNMENT_TEXT_END:
Gilles Debunne978a8ff2012-02-22 14:27:12 -08005822 mLayoutAlignment = Layout.Alignment.ALIGN_OPPOSITE;
Doug Feltc0ccf0c2011-06-23 16:13:18 -07005823 break;
Fabrice Di Meglio9da0f8a2012-03-13 19:37:57 -07005824 case TEXT_ALIGNMENT_CENTER:
Gilles Debunne978a8ff2012-02-22 14:27:12 -08005825 mLayoutAlignment = Layout.Alignment.ALIGN_CENTER;
Doug Feltc0ccf0c2011-06-23 16:13:18 -07005826 break;
Fabrice Di Meglio9da0f8a2012-03-13 19:37:57 -07005827 case TEXT_ALIGNMENT_VIEW_START:
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07005828 mLayoutAlignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
Fabrice Di Meglio9da0f8a2012-03-13 19:37:57 -07005829 Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
5830 break;
5831 case TEXT_ALIGNMENT_VIEW_END:
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07005832 mLayoutAlignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
Fabrice Di Meglio9da0f8a2012-03-13 19:37:57 -07005833 Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
5834 break;
5835 case TEXT_ALIGNMENT_INHERIT:
5836 // This should never happen as we have already resolved the text alignment
5837 // but better safe than sorry so we just fall through
Doug Feltc0ccf0c2011-06-23 16:13:18 -07005838 default:
Gilles Debunne978a8ff2012-02-22 14:27:12 -08005839 mLayoutAlignment = Layout.Alignment.ALIGN_NORMAL;
Doug Feltc0ccf0c2011-06-23 16:13:18 -07005840 break;
5841 }
Doug Feltc0ccf0c2011-06-23 16:13:18 -07005842 }
5843 return mLayoutAlignment;
5844 }
5845
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005846 /**
5847 * The width passed in is now the desired layout width,
5848 * not the full view width with padding.
5849 * {@hide}
5850 */
Gilles Debunne287d6c62011-10-05 18:22:11 -07005851 protected void makeNewLayout(int wantWidth, int hintWidth,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005852 BoringLayout.Metrics boring,
5853 BoringLayout.Metrics hintBoring,
5854 int ellipsisWidth, boolean bringIntoView) {
5855 stopMarquee();
5856
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07005857 // Update "old" cached values
5858 mOldMaximum = mMaximum;
5859 mOldMaxMode = mMaxMode;
5860
Gilles Debunne83051b82012-02-24 20:01:13 -08005861 mHighlightPathBogus = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005862
Gilles Debunne287d6c62011-10-05 18:22:11 -07005863 if (wantWidth < 0) {
5864 wantWidth = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005865 }
5866 if (hintWidth < 0) {
5867 hintWidth = 0;
5868 }
5869
Doug Feltc0ccf0c2011-06-23 16:13:18 -07005870 Layout.Alignment alignment = getLayoutAlignment();
Gilles Debunne60e21862012-01-30 15:04:14 -08005871 boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
Adam Powell282e3772011-08-30 16:51:11 -07005872 final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE &&
5873 mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
5874 TruncateAt effectiveEllipsize = mEllipsize;
5875 if (mEllipsize == TruncateAt.MARQUEE &&
5876 mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
Fabrice Di Megliocb332642011-09-23 19:08:04 -07005877 effectiveEllipsize = TruncateAt.END_SMALL;
Adam Powell282e3772011-08-30 16:51:11 -07005878 }
Romain Guy4dc4f732009-06-19 15:16:40 -07005879
Doug Feltcb3791202011-07-07 11:57:48 -07005880 if (mTextDir == null) {
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07005881 mTextDir = getTextDirectionHeuristic();
Doug Feltcb3791202011-07-07 11:57:48 -07005882 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005883
Gilles Debunne287d6c62011-10-05 18:22:11 -07005884 mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
Adam Powell282e3772011-08-30 16:51:11 -07005885 effectiveEllipsize, effectiveEllipsize == mEllipsize);
5886 if (switchEllipsize) {
5887 TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE ?
5888 TruncateAt.END : TruncateAt.MARQUEE;
Gilles Debunne287d6c62011-10-05 18:22:11 -07005889 mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
Adam Powell282e3772011-08-30 16:51:11 -07005890 shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005891 }
5892
Romain Guy4dc4f732009-06-19 15:16:40 -07005893 shouldEllipsize = mEllipsize != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005894 mHintLayout = null;
5895
5896 if (mHint != null) {
Gilles Debunne287d6c62011-10-05 18:22:11 -07005897 if (shouldEllipsize) hintWidth = wantWidth;
Romain Guy4dc4f732009-06-19 15:16:40 -07005898
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005899 if (hintBoring == UNKNOWN_BORING) {
Doug Feltcb3791202011-07-07 11:57:48 -07005900 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005901 mHintBoring);
5902 if (hintBoring != null) {
5903 mHintBoring = hintBoring;
5904 }
5905 }
5906
5907 if (hintBoring != null) {
Romain Guy4dc4f732009-06-19 15:16:40 -07005908 if (hintBoring.width <= hintWidth &&
5909 (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005910 if (mSavedHintLayout != null) {
5911 mHintLayout = mSavedHintLayout.
5912 replaceOrMake(mHint, mTextPaint,
Romain Guy4dc4f732009-06-19 15:16:40 -07005913 hintWidth, alignment, mSpacingMult, mSpacingAdd,
5914 hintBoring, mIncludePad);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005915 } else {
5916 mHintLayout = BoringLayout.make(mHint, mTextPaint,
Romain Guy4dc4f732009-06-19 15:16:40 -07005917 hintWidth, alignment, mSpacingMult, mSpacingAdd,
5918 hintBoring, mIncludePad);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005919 }
5920
5921 mSavedHintLayout = (BoringLayout) mHintLayout;
Romain Guy4dc4f732009-06-19 15:16:40 -07005922 } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
5923 if (mSavedHintLayout != null) {
5924 mHintLayout = mSavedHintLayout.
5925 replaceOrMake(mHint, mTextPaint,
5926 hintWidth, alignment, mSpacingMult, mSpacingAdd,
5927 hintBoring, mIncludePad, mEllipsize,
5928 ellipsisWidth);
5929 } else {
5930 mHintLayout = BoringLayout.make(mHint, mTextPaint,
5931 hintWidth, alignment, mSpacingMult, mSpacingAdd,
5932 hintBoring, mIncludePad, mEllipsize,
5933 ellipsisWidth);
5934 }
5935 } else if (shouldEllipsize) {
5936 mHintLayout = new StaticLayout(mHint,
5937 0, mHint.length(),
Doug Feltcb3791202011-07-07 11:57:48 -07005938 mTextPaint, hintWidth, alignment, mTextDir, mSpacingMult,
Romain Guy4dc4f732009-06-19 15:16:40 -07005939 mSpacingAdd, mIncludePad, mEllipsize,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07005940 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005941 } else {
5942 mHintLayout = new StaticLayout(mHint, mTextPaint,
Doug Feltcb3791202011-07-07 11:57:48 -07005943 hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005944 mIncludePad);
5945 }
Romain Guy4dc4f732009-06-19 15:16:40 -07005946 } else if (shouldEllipsize) {
5947 mHintLayout = new StaticLayout(mHint,
5948 0, mHint.length(),
Doug Feltcb3791202011-07-07 11:57:48 -07005949 mTextPaint, hintWidth, alignment, mTextDir, mSpacingMult,
Romain Guy4dc4f732009-06-19 15:16:40 -07005950 mSpacingAdd, mIncludePad, mEllipsize,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07005951 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005952 } else {
5953 mHintLayout = new StaticLayout(mHint, mTextPaint,
Doug Feltcb3791202011-07-07 11:57:48 -07005954 hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005955 mIncludePad);
5956 }
5957 }
5958
5959 if (bringIntoView) {
5960 registerForPreDraw();
5961 }
5962
5963 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
Romain Guy939151f2009-04-08 14:22:40 -07005964 if (!compressText(ellipsisWidth)) {
5965 final int height = mLayoutParams.height;
5966 // If the size of the view does not depend on the size of the text, try to
5967 // start the marquee immediately
Romain Guy980a9382010-01-08 15:06:28 -08005968 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
Romain Guy939151f2009-04-08 14:22:40 -07005969 startMarquee();
5970 } else {
5971 // Defer the start of the marquee until we know our width (see setFrame())
5972 mRestartMarquee = true;
5973 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005974 }
5975 }
Gilles Debunnef788a9f2010-07-22 10:17:23 -07005976
5977 // CursorControllers need a non-null mLayout
Gilles Debunne2d373a12012-04-20 15:32:19 -07005978 if (mEditor != null) mEditor.prepareCursorControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005979 }
5980
Gilles Debunne287d6c62011-10-05 18:22:11 -07005981 private Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
Adam Powell282e3772011-08-30 16:51:11 -07005982 Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
5983 boolean useSaved) {
5984 Layout result = null;
5985 if (mText instanceof Spannable) {
Gilles Debunne287d6c62011-10-05 18:22:11 -07005986 result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth,
Adam Powell282e3772011-08-30 16:51:11 -07005987 alignment, mTextDir, mSpacingMult,
Gilles Debunne60e21862012-01-30 15:04:14 -08005988 mSpacingAdd, mIncludePad, getKeyListener() == null ? effectiveEllipsize : null,
Fabrice Di Meglioad0b0512011-10-04 17:21:26 -07005989 ellipsisWidth);
Adam Powell282e3772011-08-30 16:51:11 -07005990 } else {
5991 if (boring == UNKNOWN_BORING) {
5992 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
5993 if (boring != null) {
5994 mBoring = boring;
5995 }
5996 }
5997
5998 if (boring != null) {
Gilles Debunne287d6c62011-10-05 18:22:11 -07005999 if (boring.width <= wantWidth &&
Adam Powell282e3772011-08-30 16:51:11 -07006000 (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
6001 if (useSaved && mSavedLayout != null) {
6002 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006003 wantWidth, alignment, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006004 boring, mIncludePad);
6005 } else {
6006 result = BoringLayout.make(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006007 wantWidth, alignment, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006008 boring, mIncludePad);
6009 }
6010
6011 if (useSaved) {
6012 mSavedLayout = (BoringLayout) result;
6013 }
Gilles Debunne287d6c62011-10-05 18:22:11 -07006014 } else if (shouldEllipsize && boring.width <= wantWidth) {
Adam Powell282e3772011-08-30 16:51:11 -07006015 if (useSaved && mSavedLayout != null) {
6016 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006017 wantWidth, alignment, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006018 boring, mIncludePad, effectiveEllipsize,
6019 ellipsisWidth);
6020 } else {
6021 result = BoringLayout.make(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006022 wantWidth, alignment, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006023 boring, mIncludePad, effectiveEllipsize,
6024 ellipsisWidth);
6025 }
6026 } else if (shouldEllipsize) {
6027 result = new StaticLayout(mTransformed,
6028 0, mTransformed.length(),
Gilles Debunne287d6c62011-10-05 18:22:11 -07006029 mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult,
Adam Powell282e3772011-08-30 16:51:11 -07006030 mSpacingAdd, mIncludePad, effectiveEllipsize,
6031 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6032 } else {
6033 result = new StaticLayout(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006034 wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006035 mIncludePad);
6036 }
6037 } else if (shouldEllipsize) {
6038 result = new StaticLayout(mTransformed,
6039 0, mTransformed.length(),
Gilles Debunne287d6c62011-10-05 18:22:11 -07006040 mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult,
Adam Powell282e3772011-08-30 16:51:11 -07006041 mSpacingAdd, mIncludePad, effectiveEllipsize,
6042 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6043 } else {
6044 result = new StaticLayout(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006045 wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006046 mIncludePad);
6047 }
6048 }
6049 return result;
6050 }
6051
Romain Guy939151f2009-04-08 14:22:40 -07006052 private boolean compressText(float width) {
Romain Guy2bffd262010-09-12 17:40:02 -07006053 if (isHardwareAccelerated()) return false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07006054
Romain Guy3373ed62009-05-04 14:13:32 -07006055 // Only compress the text if it hasn't been compressed by the previous pass
6056 if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX &&
6057 mTextPaint.getTextScaleX() == 1.0f) {
Romain Guy939151f2009-04-08 14:22:40 -07006058 final float textWidth = mLayout.getLineWidth(0);
Romain Guy3373ed62009-05-04 14:13:32 -07006059 final float overflow = (textWidth + 1.0f - width) / width;
Romain Guy939151f2009-04-08 14:22:40 -07006060 if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
6061 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
6062 post(new Runnable() {
6063 public void run() {
6064 requestLayout();
6065 }
6066 });
6067 return true;
6068 }
6069 }
6070
6071 return false;
6072 }
6073
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006074 private static int desired(Layout layout) {
6075 int n = layout.getLineCount();
6076 CharSequence text = layout.getText();
6077 float max = 0;
6078
6079 // if any line was wrapped, we can't use it.
6080 // but it's ok for the last line not to have a newline
6081
6082 for (int i = 0; i < n - 1; i++) {
6083 if (text.charAt(layout.getLineEnd(i) - 1) != '\n')
6084 return -1;
6085 }
6086
6087 for (int i = 0; i < n; i++) {
6088 max = Math.max(max, layout.getLineWidth(i));
6089 }
6090
6091 return (int) FloatMath.ceil(max);
6092 }
6093
6094 /**
6095 * Set whether the TextView includes extra top and bottom padding to make
6096 * room for accents that go above the normal ascent and descent.
6097 * The default is true.
6098 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07006099 * @see #getIncludeFontPadding()
6100 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006101 * @attr ref android.R.styleable#TextView_includeFontPadding
6102 */
6103 public void setIncludeFontPadding(boolean includepad) {
Gilles Debunne22378292011-08-12 10:38:52 -07006104 if (mIncludePad != includepad) {
6105 mIncludePad = includepad;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006106
Gilles Debunne22378292011-08-12 10:38:52 -07006107 if (mLayout != null) {
6108 nullLayouts();
6109 requestLayout();
6110 invalidate();
6111 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006112 }
6113 }
6114
Gilles Debunnef03acef2012-04-30 19:26:19 -07006115 /**
6116 * Gets whether the TextView includes extra top and bottom padding to make
6117 * room for accents that go above the normal ascent and descent.
6118 *
6119 * @see #setIncludeFontPadding(boolean)
6120 *
6121 * @attr ref android.R.styleable#TextView_includeFontPadding
6122 */
6123 public boolean getIncludeFontPadding() {
6124 return mIncludePad;
6125 }
6126
Romain Guy4dc4f732009-06-19 15:16:40 -07006127 private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006128
6129 @Override
6130 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
6131 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
6132 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
6133 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
6134 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
6135
6136 int width;
6137 int height;
6138
6139 BoringLayout.Metrics boring = UNKNOWN_BORING;
6140 BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
6141
Doug Feltcb3791202011-07-07 11:57:48 -07006142 if (mTextDir == null) {
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07006143 getTextDirectionHeuristic();
Doug Feltcb3791202011-07-07 11:57:48 -07006144 }
6145
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006146 int des = -1;
6147 boolean fromexisting = false;
6148
6149 if (widthMode == MeasureSpec.EXACTLY) {
6150 // Parent has told us how big to be. So be it.
6151 width = widthSize;
6152 } else {
6153 if (mLayout != null && mEllipsize == null) {
6154 des = desired(mLayout);
6155 }
6156
6157 if (des < 0) {
Doug Feltcb3791202011-07-07 11:57:48 -07006158 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006159 if (boring != null) {
6160 mBoring = boring;
6161 }
6162 } else {
6163 fromexisting = true;
6164 }
6165
6166 if (boring == null || boring == UNKNOWN_BORING) {
6167 if (des < 0) {
Romain Guy4dc4f732009-06-19 15:16:40 -07006168 des = (int) FloatMath.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006169 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006170 width = des;
6171 } else {
6172 width = boring.width;
6173 }
6174
6175 final Drawables dr = mDrawables;
6176 if (dr != null) {
6177 width = Math.max(width, dr.mDrawableWidthTop);
6178 width = Math.max(width, dr.mDrawableWidthBottom);
6179 }
6180
6181 if (mHint != null) {
6182 int hintDes = -1;
6183 int hintWidth;
6184
Romain Guy4dc4f732009-06-19 15:16:40 -07006185 if (mHintLayout != null && mEllipsize == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006186 hintDes = desired(mHintLayout);
6187 }
6188
6189 if (hintDes < 0) {
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006190 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006191 if (hintBoring != null) {
6192 mHintBoring = hintBoring;
6193 }
6194 }
6195
6196 if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
6197 if (hintDes < 0) {
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006198 hintDes = (int) FloatMath.ceil(Layout.getDesiredWidth(mHint, mTextPaint));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006199 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006200 hintWidth = hintDes;
6201 } else {
6202 hintWidth = hintBoring.width;
6203 }
6204
6205 if (hintWidth > width) {
6206 width = hintWidth;
6207 }
6208 }
6209
6210 width += getCompoundPaddingLeft() + getCompoundPaddingRight();
6211
6212 if (mMaxWidthMode == EMS) {
6213 width = Math.min(width, mMaxWidth * getLineHeight());
6214 } else {
6215 width = Math.min(width, mMaxWidth);
6216 }
6217
6218 if (mMinWidthMode == EMS) {
6219 width = Math.max(width, mMinWidth * getLineHeight());
6220 } else {
6221 width = Math.max(width, mMinWidth);
6222 }
6223
6224 // Check against our minimum width
6225 width = Math.max(width, getSuggestedMinimumWidth());
6226
6227 if (widthMode == MeasureSpec.AT_MOST) {
6228 width = Math.min(widthSize, width);
6229 }
6230 }
6231
6232 int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
6233 int unpaddedWidth = want;
Gilles Debunne9a80a652011-01-31 12:56:07 -08006234
Jeff Brown033a0012011-11-11 15:30:16 -08006235 if (mHorizontallyScrolling) want = VERY_WIDE;
Gilles Debunne9a80a652011-01-31 12:56:07 -08006236
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006237 int hintWant = want;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07006238 int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006239
6240 if (mLayout == null) {
6241 makeNewLayout(want, hintWant, boring, hintBoring,
Romain Guy4dc4f732009-06-19 15:16:40 -07006242 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006243 } else {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07006244 final boolean layoutChanged = (mLayout.getWidth() != want) ||
6245 (hintWidth != hintWant) ||
6246 (mLayout.getEllipsizedWidth() !=
6247 width - getCompoundPaddingLeft() - getCompoundPaddingRight());
6248
6249 final boolean widthChanged = (mHint == null) &&
6250 (mEllipsize == null) &&
6251 (want > mLayout.getWidth()) &&
6252 (mLayout instanceof BoringLayout || (fromexisting && des >= 0 && des <= want));
6253
6254 final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
6255
6256 if (layoutChanged || maximumChanged) {
6257 if (!maximumChanged && widthChanged) {
6258 mLayout.increaseWidthTo(want);
6259 } else {
6260 makeNewLayout(want, hintWant, boring, hintBoring,
6261 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
6262 }
6263 } else {
6264 // Nothing has changed
6265 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006266 }
6267
6268 if (heightMode == MeasureSpec.EXACTLY) {
6269 // Parent has told us how big to be. So be it.
6270 height = heightSize;
6271 mDesiredHeightAtMeasure = -1;
6272 } else {
6273 int desired = getDesiredHeight();
6274
6275 height = desired;
6276 mDesiredHeightAtMeasure = desired;
6277
6278 if (heightMode == MeasureSpec.AT_MOST) {
Christoffer Gurell1d05c7c2009-10-12 15:53:39 +02006279 height = Math.min(desired, heightSize);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006280 }
6281 }
6282
Romain Guy4dc4f732009-06-19 15:16:40 -07006283 int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006284 if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
Romain Guy4dc4f732009-06-19 15:16:40 -07006285 unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006286 }
6287
6288 /*
6289 * We didn't let makeNewLayout() register to bring the cursor into view,
6290 * so do it here if there is any possibility that it is needed.
6291 */
6292 if (mMovement != null ||
6293 mLayout.getWidth() > unpaddedWidth ||
6294 mLayout.getHeight() > unpaddedHeight) {
6295 registerForPreDraw();
6296 } else {
6297 scrollTo(0, 0);
6298 }
6299
6300 setMeasuredDimension(width, height);
6301 }
6302
6303 private int getDesiredHeight() {
Romain Guy4dc4f732009-06-19 15:16:40 -07006304 return Math.max(
6305 getDesiredHeight(mLayout, true),
6306 getDesiredHeight(mHintLayout, mEllipsize != null));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006307 }
6308
6309 private int getDesiredHeight(Layout layout, boolean cap) {
6310 if (layout == null) {
6311 return 0;
6312 }
6313
6314 int linecount = layout.getLineCount();
6315 int pad = getCompoundPaddingTop() + getCompoundPaddingBottom();
6316 int desired = layout.getLineTop(linecount);
6317
6318 final Drawables dr = mDrawables;
6319 if (dr != null) {
6320 desired = Math.max(desired, dr.mDrawableHeightLeft);
6321 desired = Math.max(desired, dr.mDrawableHeightRight);
6322 }
6323
6324 desired += pad;
6325
6326 if (mMaxMode == LINES) {
6327 /*
6328 * Don't cap the hint to a certain number of lines.
6329 * (Do cap it, though, if we have a maximum pixel height.)
6330 */
6331 if (cap) {
6332 if (linecount > mMaximum) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -08006333 desired = layout.getLineTop(mMaximum);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006334
6335 if (dr != null) {
6336 desired = Math.max(desired, dr.mDrawableHeightLeft);
6337 desired = Math.max(desired, dr.mDrawableHeightRight);
6338 }
6339
6340 desired += pad;
6341 linecount = mMaximum;
6342 }
6343 }
6344 } else {
6345 desired = Math.min(desired, mMaximum);
6346 }
6347
6348 if (mMinMode == LINES) {
6349 if (linecount < mMinimum) {
6350 desired += getLineHeight() * (mMinimum - linecount);
6351 }
6352 } else {
6353 desired = Math.max(desired, mMinimum);
6354 }
6355
6356 // Check against our minimum height
6357 desired = Math.max(desired, getSuggestedMinimumHeight());
6358
6359 return desired;
6360 }
6361
6362 /**
6363 * Check whether a change to the existing text layout requires a
6364 * new view layout.
6365 */
6366 private void checkForResize() {
6367 boolean sizeChanged = false;
6368
6369 if (mLayout != null) {
6370 // Check if our width changed
6371 if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
6372 sizeChanged = true;
6373 invalidate();
6374 }
6375
6376 // Check if our height changed
6377 if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
6378 int desiredHeight = getDesiredHeight();
6379
6380 if (desiredHeight != this.getHeight()) {
6381 sizeChanged = true;
6382 }
Romain Guy980a9382010-01-08 15:06:28 -08006383 } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006384 if (mDesiredHeightAtMeasure >= 0) {
6385 int desiredHeight = getDesiredHeight();
6386
6387 if (desiredHeight != mDesiredHeightAtMeasure) {
6388 sizeChanged = true;
6389 }
6390 }
6391 }
6392 }
6393
6394 if (sizeChanged) {
6395 requestLayout();
6396 // caller will have already invalidated
6397 }
6398 }
6399
6400 /**
6401 * Check whether entirely new text requires a new view layout
6402 * or merely a new text layout.
6403 */
6404 private void checkForRelayout() {
6405 // If we have a fixed width, we can just swap in a new text layout
6406 // if the text height stays the same or if the view height is fixed.
6407
6408 if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||
6409 (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&
6410 (mHint == null || mHintLayout != null) &&
6411 (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
6412 // Static width, so try making a new text layout.
6413
6414 int oldht = mLayout.getHeight();
6415 int want = mLayout.getWidth();
6416 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
6417
6418 /*
6419 * No need to bring the text into view, since the size is not
6420 * changing (unless we do the requestLayout(), in which case it
6421 * will happen at measure).
6422 */
6423 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
Romain Guye1e0dc82009-11-03 17:21:04 -08006424 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
6425 false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006426
Romain Guye1e0dc82009-11-03 17:21:04 -08006427 if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
6428 // In a fixed-height view, so use our new text layout.
6429 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
Romain Guy980a9382010-01-08 15:06:28 -08006430 mLayoutParams.height != LayoutParams.MATCH_PARENT) {
Romain Guye1e0dc82009-11-03 17:21:04 -08006431 invalidate();
6432 return;
6433 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006434
Romain Guye1e0dc82009-11-03 17:21:04 -08006435 // Dynamic height, but height has stayed the same,
6436 // so use our new text layout.
6437 if (mLayout.getHeight() == oldht &&
6438 (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
6439 invalidate();
6440 return;
6441 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006442 }
6443
6444 // We lose: the height has changed and we have a dynamic height.
6445 // Request a new view layout using our new text layout.
6446 requestLayout();
6447 invalidate();
6448 } else {
6449 // Dynamic width, so we have no choice but to request a new
6450 // view layout with a new text layout.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006451 nullLayouts();
6452 requestLayout();
6453 invalidate();
6454 }
6455 }
6456
Gilles Debunne954325e2012-01-25 11:57:06 -08006457 @Override
6458 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
6459 super.onLayout(changed, left, top, right, bottom);
Raph Levienf5c1a872012-10-15 17:22:26 -07006460 if (mDeferScroll >= 0) {
6461 int curs = mDeferScroll;
6462 mDeferScroll = -1;
Raph Levien8b179692012-10-16 14:32:47 -07006463 bringPointIntoView(Math.min(curs, mText.length()));
Raph Levienf5c1a872012-10-15 17:22:26 -07006464 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006465 if (changed && mEditor != null) mEditor.invalidateTextDisplayList();
Gilles Debunne954325e2012-01-25 11:57:06 -08006466 }
6467
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006468 private boolean isShowingHint() {
6469 return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint);
6470 }
6471
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006472 /**
6473 * Returns true if anything changed.
6474 */
6475 private boolean bringTextIntoView() {
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006476 Layout layout = isShowingHint() ? mHintLayout : mLayout;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006477 int line = 0;
6478 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006479 line = layout.getLineCount() - 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006480 }
6481
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006482 Layout.Alignment a = layout.getParagraphAlignment(line);
6483 int dir = layout.getParagraphDirection(line);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006484 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6485 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006486 int ht = layout.getHeight();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006487
6488 int scrollx, scrolly;
6489
Doug Felt25b9f422011-07-11 13:48:37 -07006490 // Convert to left, center, or right alignment.
6491 if (a == Layout.Alignment.ALIGN_NORMAL) {
6492 a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_LEFT :
6493 Layout.Alignment.ALIGN_RIGHT;
6494 } else if (a == Layout.Alignment.ALIGN_OPPOSITE){
6495 a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_RIGHT :
6496 Layout.Alignment.ALIGN_LEFT;
6497 }
6498
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006499 if (a == Layout.Alignment.ALIGN_CENTER) {
6500 /*
6501 * Keep centered if possible, or, if it is too wide to fit,
6502 * keep leading edge in view.
6503 */
6504
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006505 int left = (int) FloatMath.floor(layout.getLineLeft(line));
6506 int right = (int) FloatMath.ceil(layout.getLineRight(line));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006507
6508 if (right - left < hspace) {
6509 scrollx = (right + left) / 2 - hspace / 2;
6510 } else {
6511 if (dir < 0) {
6512 scrollx = right - hspace;
6513 } else {
6514 scrollx = left;
6515 }
6516 }
Fabrice Di Megliod2b5d1c2011-07-13 19:38:17 -07006517 } else if (a == Layout.Alignment.ALIGN_RIGHT) {
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006518 int right = (int) FloatMath.ceil(layout.getLineRight(line));
Doug Felt25b9f422011-07-11 13:48:37 -07006519 scrollx = right - hspace;
Fabrice Di Megliod2b5d1c2011-07-13 19:38:17 -07006520 } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006521 scrollx = (int) FloatMath.floor(layout.getLineLeft(line));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006522 }
6523
6524 if (ht < vspace) {
6525 scrolly = 0;
6526 } else {
6527 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
6528 scrolly = ht - vspace;
6529 } else {
6530 scrolly = 0;
6531 }
6532 }
6533
6534 if (scrollx != mScrollX || scrolly != mScrollY) {
6535 scrollTo(scrollx, scrolly);
6536 return true;
6537 } else {
6538 return false;
6539 }
6540 }
6541
6542 /**
6543 * Move the point, specified by the offset, into the view if it is needed.
6544 * This has to be called after layout. Returns true if anything changed.
6545 */
6546 public boolean bringPointIntoView(int offset) {
Raph Levienf5c1a872012-10-15 17:22:26 -07006547 if (isLayoutRequested()) {
6548 mDeferScroll = offset;
6549 return false;
6550 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006551 boolean changed = false;
6552
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006553 Layout layout = isShowingHint() ? mHintLayout: mLayout;
Gilles Debunne176ee3d2011-07-16 13:28:41 -07006554
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006555 if (layout == null) return changed;
6556
6557 int line = layout.getLineForOffset(offset);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006558
6559 // FIXME: Is it okay to truncate this, or should we round?
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006560 final int x = (int)layout.getPrimaryHorizontal(offset);
6561 final int top = layout.getLineTop(line);
6562 final int bottom = layout.getLineTop(line + 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006563
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006564 int left = (int) FloatMath.floor(layout.getLineLeft(line));
6565 int right = (int) FloatMath.ceil(layout.getLineRight(line));
6566 int ht = layout.getHeight();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006567
6568 int grav;
6569
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006570 switch (layout.getParagraphAlignment(line)) {
Doug Felt25b9f422011-07-11 13:48:37 -07006571 case ALIGN_LEFT:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006572 grav = 1;
6573 break;
Doug Felt25b9f422011-07-11 13:48:37 -07006574 case ALIGN_RIGHT:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006575 grav = -1;
6576 break;
Doug Felt25b9f422011-07-11 13:48:37 -07006577 case ALIGN_NORMAL:
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006578 grav = layout.getParagraphDirection(line);
Doug Felt25b9f422011-07-11 13:48:37 -07006579 break;
6580 case ALIGN_OPPOSITE:
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006581 grav = -layout.getParagraphDirection(line);
Doug Felt25b9f422011-07-11 13:48:37 -07006582 break;
6583 case ALIGN_CENTER:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006584 default:
6585 grav = 0;
Doug Felt25b9f422011-07-11 13:48:37 -07006586 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006587 }
6588
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006589 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6590 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
6591
6592 int hslack = (bottom - top) / 2;
6593 int vslack = hslack;
6594
6595 if (vslack > vspace / 4)
6596 vslack = vspace / 4;
6597 if (hslack > hspace / 4)
6598 hslack = hspace / 4;
6599
6600 int hs = mScrollX;
6601 int vs = mScrollY;
6602
6603 if (top - vs < vslack)
6604 vs = top - vslack;
6605 if (bottom - vs > vspace - vslack)
6606 vs = bottom - (vspace - vslack);
6607 if (ht - vs < vspace)
6608 vs = ht - vspace;
6609 if (0 - vs > 0)
6610 vs = 0;
6611
6612 if (grav != 0) {
6613 if (x - hs < hslack) {
6614 hs = x - hslack;
6615 }
6616 if (x - hs > hspace - hslack) {
6617 hs = x - (hspace - hslack);
6618 }
6619 }
6620
6621 if (grav < 0) {
6622 if (left - hs > 0)
6623 hs = left;
6624 if (right - hs < hspace)
6625 hs = right - hspace;
6626 } else if (grav > 0) {
6627 if (right - hs < hspace)
6628 hs = right - hspace;
6629 if (left - hs > 0)
6630 hs = left;
6631 } else /* grav == 0 */ {
6632 if (right - left <= hspace) {
6633 /*
6634 * If the entire text fits, center it exactly.
6635 */
6636 hs = left - (hspace - (right - left)) / 2;
6637 } else if (x > right - hslack) {
6638 /*
6639 * If we are near the right edge, keep the right edge
6640 * at the edge of the view.
6641 */
6642 hs = right - hspace;
6643 } else if (x < left + hslack) {
6644 /*
6645 * If we are near the left edge, keep the left edge
6646 * at the edge of the view.
6647 */
6648 hs = left;
6649 } else if (left > hs) {
6650 /*
6651 * Is there whitespace visible at the left? Fix it if so.
6652 */
6653 hs = left;
6654 } else if (right < hs + hspace) {
6655 /*
6656 * Is there whitespace visible at the right? Fix it if so.
6657 */
6658 hs = right - hspace;
6659 } else {
6660 /*
6661 * Otherwise, float as needed.
6662 */
6663 if (x - hs < hslack) {
6664 hs = x - hslack;
6665 }
6666 if (x - hs > hspace - hslack) {
6667 hs = x - (hspace - hslack);
6668 }
6669 }
6670 }
6671
6672 if (hs != mScrollX || vs != mScrollY) {
6673 if (mScroller == null) {
6674 scrollTo(hs, vs);
6675 } else {
6676 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
6677 int dx = hs - mScrollX;
6678 int dy = vs - mScrollY;
6679
6680 if (duration > ANIMATED_SCROLL_GAP) {
6681 mScroller.startScroll(mScrollX, mScrollY, dx, dy);
Mike Cleronf116bf82009-09-27 19:14:12 -07006682 awakenScrollBars(mScroller.getDuration());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006683 invalidate();
6684 } else {
6685 if (!mScroller.isFinished()) {
6686 mScroller.abortAnimation();
6687 }
6688
6689 scrollBy(dx, dy);
6690 }
6691
6692 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
6693 }
6694
6695 changed = true;
6696 }
6697
6698 if (isFocused()) {
Gilles Debunne716dbf62011-03-07 18:12:10 -08006699 // This offsets because getInterestingRect() is in terms of viewport coordinates, but
6700 // requestRectangleOnScreen() is in terms of content coordinates.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006701
Dianne Hackborn70a3f672011-08-08 14:32:41 -07006702 // The offsets here are to ensure the rectangle we are using is
6703 // within our view bounds, in case the cursor is on the far left
6704 // or right. If it isn't withing the bounds, then this request
6705 // will be ignored.
Gilles Debunne60e21862012-01-30 15:04:14 -08006706 if (mTempRect == null) mTempRect = new Rect();
Dianne Hackborn70a3f672011-08-08 14:32:41 -07006707 mTempRect.set(x - 2, top, x + 2, bottom);
Gilles Debunne716dbf62011-03-07 18:12:10 -08006708 getInterestingRect(mTempRect, line);
6709 mTempRect.offset(mScrollX, mScrollY);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006710
Gilles Debunne716dbf62011-03-07 18:12:10 -08006711 if (requestRectangleOnScreen(mTempRect)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006712 changed = true;
6713 }
6714 }
6715
6716 return changed;
6717 }
6718
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006719 /**
6720 * Move the cursor, if needed, so that it is at an offset that is visible
6721 * to the user. This will not move the cursor if it represents more than
6722 * one character (a selection range). This will only work if the
6723 * TextView contains spannable text; otherwise it will do nothing.
Gilles Debunnebbb5d6e2010-06-21 16:21:51 -07006724 *
Gilles Debunne57f4e5b2010-06-21 16:21:51 -07006725 * @return True if the cursor was actually moved, false otherwise.
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006726 */
6727 public boolean moveCursorToVisibleOffset() {
6728 if (!(mText instanceof Spannable)) {
6729 return false;
6730 }
Gilles Debunne05336272010-07-09 20:13:45 -07006731 int start = getSelectionStart();
6732 int end = getSelectionEnd();
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006733 if (start != end) {
6734 return false;
6735 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006736
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006737 // First: make sure the line is visible on screen:
Gilles Debunne2d373a12012-04-20 15:32:19 -07006738
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006739 int line = mLayout.getLineForOffset(start);
6740
6741 final int top = mLayout.getLineTop(line);
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07006742 final int bottom = mLayout.getLineTop(line + 1);
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006743 final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
6744 int vslack = (bottom - top) / 2;
6745 if (vslack > vspace / 4)
6746 vslack = vspace / 4;
6747 final int vs = mScrollY;
6748
6749 if (top < (vs+vslack)) {
6750 line = mLayout.getLineForVertical(vs+vslack+(bottom-top));
6751 } else if (bottom > (vspace+vs-vslack)) {
6752 line = mLayout.getLineForVertical(vspace+vs-vslack-(bottom-top));
6753 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006754
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006755 // Next: make sure the character is visible on screen:
Gilles Debunne2d373a12012-04-20 15:32:19 -07006756
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006757 final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6758 final int hs = mScrollX;
6759 final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
6760 final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs);
Gilles Debunne2d373a12012-04-20 15:32:19 -07006761
Doug Feltc982f602010-05-25 11:51:40 -07006762 // line might contain bidirectional text
6763 final int lowChar = leftChar < rightChar ? leftChar : rightChar;
6764 final int highChar = leftChar > rightChar ? leftChar : rightChar;
6765
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006766 int newStart = start;
Doug Feltc982f602010-05-25 11:51:40 -07006767 if (newStart < lowChar) {
6768 newStart = lowChar;
6769 } else if (newStart > highChar) {
6770 newStart = highChar;
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006771 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006772
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006773 if (newStart != start) {
6774 Selection.setSelection((Spannable)mText, newStart);
6775 return true;
6776 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006777
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006778 return false;
6779 }
6780
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006781 @Override
6782 public void computeScroll() {
6783 if (mScroller != null) {
6784 if (mScroller.computeScrollOffset()) {
6785 mScrollX = mScroller.getCurrX();
6786 mScrollY = mScroller.getCurrY();
Romain Guy0fd89bf2011-01-26 15:41:30 -08006787 invalidateParentCaches();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006788 postInvalidate(); // So we draw again
6789 }
6790 }
6791 }
6792
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07006793 private void getInterestingRect(Rect r, int line) {
6794 convertFromViewportToContentCoordinates(r);
6795
6796 // Rectangle can can be expanded on first and last line to take
6797 // padding into account.
6798 // TODO Take left/right padding into account too?
6799 if (line == 0) r.top -= getExtendedPaddingTop();
6800 if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
6801 }
6802
6803 private void convertFromViewportToContentCoordinates(Rect r) {
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07006804 final int horizontalOffset = viewportToContentHorizontalOffset();
6805 r.left += horizontalOffset;
6806 r.right += horizontalOffset;
6807
6808 final int verticalOffset = viewportToContentVerticalOffset();
6809 r.top += verticalOffset;
6810 r.bottom += verticalOffset;
6811 }
6812
Gilles Debunned88876a2012-03-16 17:34:04 -07006813 int viewportToContentHorizontalOffset() {
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07006814 return getCompoundPaddingLeft() - mScrollX;
6815 }
6816
Gilles Debunned88876a2012-03-16 17:34:04 -07006817 int viewportToContentVerticalOffset() {
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07006818 int offset = getExtendedPaddingTop() - mScrollY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006819 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07006820 offset += getVerticalOffset(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006821 }
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07006822 return offset;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006823 }
6824
6825 @Override
6826 public void debug(int depth) {
6827 super.debug(depth);
6828
6829 String output = debugIndent(depth);
6830 output += "frame={" + mLeft + ", " + mTop + ", " + mRight
6831 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
6832 + "} ";
6833
6834 if (mText != null) {
6835
6836 output += "mText=\"" + mText + "\" ";
6837 if (mLayout != null) {
6838 output += "mLayout width=" + mLayout.getWidth()
6839 + " height=" + mLayout.getHeight();
6840 }
6841 } else {
6842 output += "mText=NULL";
6843 }
6844 Log.d(VIEW_LOG_TAG, output);
6845 }
6846
6847 /**
6848 * Convenience for {@link Selection#getSelectionStart}.
6849 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006850 @ViewDebug.ExportedProperty(category = "text")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006851 public int getSelectionStart() {
6852 return Selection.getSelectionStart(getText());
6853 }
6854
6855 /**
6856 * Convenience for {@link Selection#getSelectionEnd}.
6857 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006858 @ViewDebug.ExportedProperty(category = "text")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006859 public int getSelectionEnd() {
6860 return Selection.getSelectionEnd(getText());
6861 }
6862
6863 /**
6864 * Return true iff there is a selection inside this text view.
6865 */
6866 public boolean hasSelection() {
Gilles Debunne03789e82010-09-07 19:07:17 -07006867 final int selectionStart = getSelectionStart();
6868 final int selectionEnd = getSelectionEnd();
6869
6870 return selectionStart >= 0 && selectionStart != selectionEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006871 }
6872
6873 /**
6874 * Sets the properties of this field (lines, horizontally scrolling,
6875 * transformation method) to be for a single-line input.
6876 *
6877 * @attr ref android.R.styleable#TextView_singleLine
6878 */
6879 public void setSingleLine() {
6880 setSingleLine(true);
6881 }
6882
6883 /**
Adam Powell7f8f79a2011-07-07 18:35:54 -07006884 * Sets the properties of this field to transform input to ALL CAPS
6885 * display. This may use a "small caps" formatting if available.
6886 * This setting will be ignored if this field is editable or selectable.
6887 *
6888 * This call replaces the current transformation method. Disabling this
6889 * will not necessarily restore the previous behavior from before this
6890 * was enabled.
6891 *
6892 * @see #setTransformationMethod(TransformationMethod)
6893 * @attr ref android.R.styleable#TextView_textAllCaps
6894 */
6895 public void setAllCaps(boolean allCaps) {
6896 if (allCaps) {
6897 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
6898 } else {
6899 setTransformationMethod(null);
6900 }
6901 }
6902
6903 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08006904 * If true, sets the properties of this field (number of lines, horizontally scrolling,
6905 * transformation method) to be for a single-line input; if false, restores these to the default
6906 * conditions.
6907 *
6908 * Note that the default conditions are not necessarily those that were in effect prior this
6909 * method, and you may want to reset these properties to your custom values.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006910 *
6911 * @attr ref android.R.styleable#TextView_singleLine
6912 */
6913 @android.view.RemotableViewMethod
6914 public void setSingleLine(boolean singleLine) {
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08006915 // Could be used, but may break backward compatibility.
6916 // if (mSingleLine == singleLine) return;
Gilles Debunned7483bf2010-11-10 10:47:45 -08006917 setInputTypeSingleLine(singleLine);
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08006918 applySingleLine(singleLine, true, true);
Gilles Debunned7483bf2010-11-10 10:47:45 -08006919 }
6920
6921 /**
6922 * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
6923 * @param singleLine
6924 */
6925 private void setInputTypeSingleLine(boolean singleLine) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07006926 if (mEditor != null &&
6927 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006928 if (singleLine) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07006929 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006930 } else {
Gilles Debunne2d373a12012-04-20 15:32:19 -07006931 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006932 }
6933 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006934 }
6935
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08006936 private void applySingleLine(boolean singleLine, boolean applyTransformation,
6937 boolean changeMaxLines) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006938 mSingleLine = singleLine;
6939 if (singleLine) {
6940 setLines(1);
6941 setHorizontallyScrolling(true);
6942 if (applyTransformation) {
Gilles Debunne91a08cf2010-11-08 17:34:49 -08006943 setTransformationMethod(SingleLineTransformationMethod.getInstance());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006944 }
6945 } else {
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08006946 if (changeMaxLines) {
6947 setMaxLines(Integer.MAX_VALUE);
6948 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006949 setHorizontallyScrolling(false);
6950 if (applyTransformation) {
6951 setTransformationMethod(null);
6952 }
6953 }
6954 }
Gilles Debunneb2316962010-12-21 17:32:43 -08006955
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006956 /**
6957 * Causes words in the text that are longer than the view is wide
6958 * to be ellipsized instead of broken in the middle. You may also
6959 * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
Kenny Roote855d132009-06-11 11:00:42 -05006960 * to constrain the text to a single line. Use <code>null</code>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006961 * to turn off ellipsizing.
6962 *
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07006963 * If {@link #setMaxLines} has been used to set two or more lines,
Gilles Debunne6435a562011-08-04 21:22:30 -07006964 * {@link android.text.TextUtils.TruncateAt#END} and
6965 * {@link android.text.TextUtils.TruncateAt#MARQUEE}* are only supported
6966 * (other ellipsizing types will not do anything).
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07006967 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006968 * @attr ref android.R.styleable#TextView_ellipsize
6969 */
6970 public void setEllipsize(TextUtils.TruncateAt where) {
Gilles Debunne22378292011-08-12 10:38:52 -07006971 // TruncateAt is an enum. != comparison is ok between these singleton objects.
6972 if (mEllipsize != where) {
6973 mEllipsize = where;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006974
Gilles Debunne22378292011-08-12 10:38:52 -07006975 if (mLayout != null) {
6976 nullLayouts();
6977 requestLayout();
6978 invalidate();
6979 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006980 }
6981 }
6982
6983 /**
6984 * Sets how many times to repeat the marquee animation. Only applied if the
6985 * TextView has marquee enabled. Set to -1 to repeat indefinitely.
6986 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07006987 * @see #getMarqueeRepeatLimit()
6988 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006989 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
6990 */
6991 public void setMarqueeRepeatLimit(int marqueeLimit) {
6992 mMarqueeRepeatLimit = marqueeLimit;
6993 }
6994
6995 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07006996 * Gets the number of times the marquee animation is repeated. Only meaningful if the
6997 * TextView has marquee enabled.
6998 *
6999 * @return the number of times the marquee animation is repeated. -1 if the animation
7000 * repeats indefinitely
7001 *
7002 * @see #setMarqueeRepeatLimit(int)
7003 *
7004 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
7005 */
7006 public int getMarqueeRepeatLimit() {
7007 return mMarqueeRepeatLimit;
7008 }
7009
7010 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007011 * Returns where, if anywhere, words that are longer than the view
7012 * is wide should be ellipsized.
7013 */
7014 @ViewDebug.ExportedProperty
7015 public TextUtils.TruncateAt getEllipsize() {
7016 return mEllipsize;
7017 }
7018
7019 /**
7020 * Set the TextView so that when it takes focus, all the text is
7021 * selected.
7022 *
7023 * @attr ref android.R.styleable#TextView_selectAllOnFocus
7024 */
7025 @android.view.RemotableViewMethod
7026 public void setSelectAllOnFocus(boolean selectAllOnFocus) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07007027 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07007028 mEditor.mSelectAllOnFocus = selectAllOnFocus;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007029
7030 if (selectAllOnFocus && !(mText instanceof Spannable)) {
7031 setText(mText, BufferType.SPANNABLE);
7032 }
7033 }
7034
7035 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07007036 * Set whether the cursor is visible. The default is true. Note that this property only
7037 * makes sense for editable TextView.
7038 *
7039 * @see #isCursorVisible()
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007040 *
7041 * @attr ref android.R.styleable#TextView_cursorVisible
7042 */
7043 @android.view.RemotableViewMethod
7044 public void setCursorVisible(boolean visible) {
Gilles Debunne60e21862012-01-30 15:04:14 -08007045 if (visible && mEditor == null) return; // visible is the default value with no edit data
Gilles Debunne5fae9962012-05-08 14:53:20 -07007046 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07007047 if (mEditor.mCursorVisible != visible) {
7048 mEditor.mCursorVisible = visible;
Gilles Debunne3d010062011-02-18 14:16:41 -08007049 invalidate();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007050
Gilles Debunne2d373a12012-04-20 15:32:19 -07007051 mEditor.makeBlink();
Gilles Debunnef788a9f2010-07-22 10:17:23 -07007052
Gilles Debunne3d010062011-02-18 14:16:41 -08007053 // InsertionPointCursorController depends on mCursorVisible
Gilles Debunne2d373a12012-04-20 15:32:19 -07007054 mEditor.prepareCursorControllers();
Gilles Debunne3d010062011-02-18 14:16:41 -08007055 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007056 }
7057
Gilles Debunnef03acef2012-04-30 19:26:19 -07007058 /**
7059 * @return whether or not the cursor is visible (assuming this TextView is editable)
7060 *
7061 * @see #setCursorVisible(boolean)
7062 *
7063 * @attr ref android.R.styleable#TextView_cursorVisible
7064 */
7065 public boolean isCursorVisible() {
7066 // true is the default value
7067 return mEditor == null ? true : mEditor.mCursorVisible;
7068 }
7069
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007070 private boolean canMarquee() {
7071 int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight());
Adam Powell282e3772011-08-30 16:51:11 -07007072 return width > 0 && (mLayout.getLineWidth(0) > width ||
7073 (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null &&
7074 mSavedMarqueeModeLayout.getLineWidth(0) > width));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007075 }
7076
7077 private void startMarquee() {
Romain Guy4dc4f732009-06-19 15:16:40 -07007078 // Do not ellipsize EditText
Gilles Debunne60e21862012-01-30 15:04:14 -08007079 if (getKeyListener() != null) return;
Romain Guy4dc4f732009-06-19 15:16:40 -07007080
Romain Guy939151f2009-04-08 14:22:40 -07007081 if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
7082 return;
7083 }
7084
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007085 if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) &&
7086 getLineCount() == 1 && canMarquee()) {
Romain Guy939151f2009-04-08 14:22:40 -07007087
Adam Powell282e3772011-08-30 16:51:11 -07007088 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
7089 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
7090 final Layout tmp = mLayout;
7091 mLayout = mSavedMarqueeModeLayout;
7092 mSavedMarqueeModeLayout = tmp;
7093 setHorizontalFadingEdgeEnabled(true);
7094 requestLayout();
7095 invalidate();
7096 }
7097
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007098 if (mMarquee == null) mMarquee = new Marquee(this);
7099 mMarquee.start(mMarqueeRepeatLimit);
7100 }
7101 }
7102
7103 private void stopMarquee() {
7104 if (mMarquee != null && !mMarquee.isStopped()) {
7105 mMarquee.stop();
7106 }
Adam Powell282e3772011-08-30 16:51:11 -07007107
7108 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
7109 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
7110 final Layout tmp = mSavedMarqueeModeLayout;
7111 mSavedMarqueeModeLayout = mLayout;
7112 mLayout = tmp;
7113 setHorizontalFadingEdgeEnabled(false);
7114 requestLayout();
7115 invalidate();
7116 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007117 }
7118
7119 private void startStopMarquee(boolean start) {
7120 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7121 if (start) {
7122 startMarquee();
7123 } else {
7124 stopMarquee();
7125 }
7126 }
7127 }
7128
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007129 /**
Gilles Debunne4469e602011-03-09 14:38:04 -08007130 * This method is called when the text is changed, in case any subclasses
7131 * would like to know.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007132 *
Gilles Debunne4469e602011-03-09 14:38:04 -08007133 * Within <code>text</code>, the <code>lengthAfter</code> characters
7134 * beginning at <code>start</code> have just replaced old text that had
7135 * length <code>lengthBefore</code>. It is an error to attempt to make
7136 * changes to <code>text</code> from this callback.
7137 *
7138 * @param text The text the TextView is displaying
7139 * @param start The offset of the start of the range of the text that was
7140 * modified
7141 * @param lengthBefore The length of the former text that has been replaced
7142 * @param lengthAfter The length of the replacement modified text
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007143 */
Gilles Debunne4469e602011-03-09 14:38:04 -08007144 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
Gilles Debunne6435a562011-08-04 21:22:30 -07007145 // intentionally empty, template pattern method can be overridden by subclasses
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007146 }
7147
7148 /**
7149 * This method is called when the selection has changed, in case any
7150 * subclasses would like to know.
Gilles Debunne2d373a12012-04-20 15:32:19 -07007151 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007152 * @param selStart The new selection start location.
7153 * @param selEnd The new selection end location.
7154 */
7155 protected void onSelectionChanged(int selStart, int selEnd) {
Svetoslav Ganova0156172011-06-26 17:55:44 -07007156 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007157 }
Svetoslav Ganova0156172011-06-26 17:55:44 -07007158
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007159 /**
7160 * Adds a TextWatcher to the list of those whose methods are called
7161 * whenever this TextView's text changes.
7162 * <p>
7163 * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
7164 * not called after {@link #setText} calls. Now, doing {@link #setText}
7165 * if there are any text changed listeners forces the buffer type to
7166 * Editable if it would not otherwise be and does call this method.
7167 */
7168 public void addTextChangedListener(TextWatcher watcher) {
7169 if (mListeners == null) {
7170 mListeners = new ArrayList<TextWatcher>();
7171 }
7172
7173 mListeners.add(watcher);
7174 }
7175
7176 /**
7177 * Removes the specified TextWatcher from the list of those whose
7178 * methods are called
7179 * whenever this TextView's text changes.
7180 */
7181 public void removeTextChangedListener(TextWatcher watcher) {
7182 if (mListeners != null) {
7183 int i = mListeners.indexOf(watcher);
7184
7185 if (i >= 0) {
7186 mListeners.remove(i);
7187 }
7188 }
7189 }
7190
Gilles Debunne6435a562011-08-04 21:22:30 -07007191 private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007192 if (mListeners != null) {
7193 final ArrayList<TextWatcher> list = mListeners;
7194 final int count = list.size();
7195 for (int i = 0; i < count; i++) {
7196 list.get(i).beforeTextChanged(text, start, before, after);
7197 }
7198 }
Gilles Debunne6435a562011-08-04 21:22:30 -07007199
7200 // The spans that are inside or intersect the modified region no longer make sense
7201 removeIntersectingSpans(start, start + before, SpellCheckSpan.class);
7202 removeIntersectingSpans(start, start + before, SuggestionSpan.class);
7203 }
7204
7205 // Removes all spans that are inside or actually overlap the start..end range
7206 private <T> void removeIntersectingSpans(int start, int end, Class<T> type) {
7207 if (!(mText instanceof Editable)) return;
7208 Editable text = (Editable) mText;
7209
7210 T[] spans = text.getSpans(start, end, type);
7211 final int length = spans.length;
7212 for (int i = 0; i < length; i++) {
7213 final int s = text.getSpanStart(spans[i]);
7214 final int e = text.getSpanEnd(spans[i]);
Gilles Debunneb062e812011-09-27 14:58:37 -07007215 // Spans that are adjacent to the edited region will be handled in
7216 // updateSpellCheckSpans. Result depends on what will be added (space or text)
Gilles Debunne6435a562011-08-04 21:22:30 -07007217 if (e == start || s == end) break;
7218 text.removeSpan(spans[i]);
7219 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007220 }
7221
7222 /**
7223 * Not private so it can be called from an inner class without going
7224 * through a thunk.
7225 */
Gilles Debunne6435a562011-08-04 21:22:30 -07007226 void sendOnTextChanged(CharSequence text, int start, int before, int after) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007227 if (mListeners != null) {
7228 final ArrayList<TextWatcher> list = mListeners;
7229 final int count = list.size();
7230 for (int i = 0; i < count; i++) {
7231 list.get(i).onTextChanged(text, start, before, after);
7232 }
7233 }
Gilles Debunne1a22db22011-11-20 22:13:21 +01007234
Gilles Debunne2d373a12012-04-20 15:32:19 -07007235 if (mEditor != null) mEditor.sendOnTextChanged(start, after);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007236 }
7237
7238 /**
7239 * Not private so it can be called from an inner class without going
7240 * through a thunk.
7241 */
7242 void sendAfterTextChanged(Editable text) {
7243 if (mListeners != null) {
7244 final ArrayList<TextWatcher> list = mListeners;
7245 final int count = list.size();
7246 for (int i = 0; i < count; i++) {
7247 list.get(i).afterTextChanged(text);
7248 }
7249 }
7250 }
7251
Gilles Debunned88876a2012-03-16 17:34:04 -07007252 void updateAfterEdit() {
7253 invalidate();
7254 int curs = getSelectionStart();
7255
7256 if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
7257 registerForPreDraw();
7258 }
7259
Raph Levienf5c1a872012-10-15 17:22:26 -07007260 checkForResize();
7261
Gilles Debunned88876a2012-03-16 17:34:04 -07007262 if (curs >= 0) {
7263 mHighlightPathBogus = true;
Gilles Debunne2d373a12012-04-20 15:32:19 -07007264 if (mEditor != null) mEditor.makeBlink();
Gilles Debunned88876a2012-03-16 17:34:04 -07007265 bringPointIntoView(curs);
7266 }
Gilles Debunned88876a2012-03-16 17:34:04 -07007267 }
7268
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007269 /**
7270 * Not private so it can be called from an inner class without going
7271 * through a thunk.
7272 */
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08007273 void handleTextChanged(CharSequence buffer, int start, int before, int after) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007274 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007275 if (ims == null || ims.mBatchEditNesting == 0) {
7276 updateAfterEdit();
7277 }
7278 if (ims != null) {
7279 ims.mContentChanged = true;
7280 if (ims.mChangedStart < 0) {
7281 ims.mChangedStart = start;
7282 ims.mChangedEnd = start+before;
7283 } else {
Viktor Yakovel964be412010-02-17 08:35:57 +01007284 ims.mChangedStart = Math.min(ims.mChangedStart, start);
7285 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007286 }
7287 ims.mChangedDelta += after-before;
7288 }
Gilles Debunne186aaf92011-09-16 14:26:12 -07007289
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007290 sendOnTextChanged(buffer, start, before, after);
7291 onTextChanged(buffer, start, before, after);
7292 }
Gilles Debunne60e21862012-01-30 15:04:14 -08007293
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007294 /**
7295 * Not private so it can be called from an inner class without going
7296 * through a thunk.
7297 */
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08007298 void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007299 // XXX Make the start and end move together if this ends up
7300 // spending too much time invalidating.
7301
7302 boolean selChanged = false;
7303 int newSelStart=-1, newSelEnd=-1;
Gilles Debunne60e21862012-01-30 15:04:14 -08007304
Gilles Debunne2d373a12012-04-20 15:32:19 -07007305 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
Gilles Debunne60e21862012-01-30 15:04:14 -08007306
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007307 if (what == Selection.SELECTION_END) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007308 selChanged = true;
7309 newSelEnd = newStart;
7310
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007311 if (oldStart >= 0 || newStart >= 0) {
7312 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
Raph Levienf5c1a872012-10-15 17:22:26 -07007313 checkForResize();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007314 registerForPreDraw();
Gilles Debunne2d373a12012-04-20 15:32:19 -07007315 if (mEditor != null) mEditor.makeBlink();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007316 }
7317 }
7318
7319 if (what == Selection.SELECTION_START) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007320 selChanged = true;
7321 newSelStart = newStart;
7322
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007323 if (oldStart >= 0 || newStart >= 0) {
7324 int end = Selection.getSelectionEnd(buf);
7325 invalidateCursor(end, oldStart, newStart);
7326 }
7327 }
7328
7329 if (selChanged) {
Gilles Debunne83051b82012-02-24 20:01:13 -08007330 mHighlightPathBogus = true;
Gilles Debunne2d373a12012-04-20 15:32:19 -07007331 if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
Gilles Debunne60e21862012-01-30 15:04:14 -08007332
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007333 if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) {
7334 if (newSelStart < 0) {
7335 newSelStart = Selection.getSelectionStart(buf);
7336 }
7337 if (newSelEnd < 0) {
7338 newSelEnd = Selection.getSelectionEnd(buf);
7339 }
7340 onSelectionChanged(newSelStart, newSelEnd);
7341 }
7342 }
Gilles Debunne8615ac92011-11-29 15:25:03 -08007343
Gilles Debunneb35ab7b2011-12-05 15:54:00 -08007344 if (what instanceof UpdateAppearance || what instanceof ParagraphStyle ||
7345 what instanceof CharacterStyle) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007346 if (ims == null || ims.mBatchEditNesting == 0) {
7347 invalidate();
Gilles Debunne83051b82012-02-24 20:01:13 -08007348 mHighlightPathBogus = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007349 checkForResize();
7350 } else {
7351 ims.mContentChanged = true;
7352 }
Gilles Debunneebc86af2012-04-20 15:10:47 -07007353 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007354 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
7355 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
Gilles Debunneebc86af2012-04-20 15:10:47 -07007356 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007357 }
7358
7359 if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
Gilles Debunne83051b82012-02-24 20:01:13 -08007360 mHighlightPathBogus = true;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07007361 if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
7362 ims.mSelectionModeChanged = true;
7363 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007364
7365 if (Selection.getSelectionStart(buf) >= 0) {
7366 if (ims == null || ims.mBatchEditNesting == 0) {
7367 invalidateCursor();
7368 } else {
7369 ims.mCursorChanged = true;
7370 }
7371 }
7372 }
Gilles Debunne6435a562011-08-04 21:22:30 -07007373
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007374 if (what instanceof ParcelableSpan) {
7375 // If this is a span that can be sent to a remote process,
7376 // the current extract editor would be interested in it.
Gilles Debunnec62589c2012-04-12 14:50:23 -07007377 if (ims != null && ims.mExtractedTextRequest != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007378 if (ims.mBatchEditNesting != 0) {
7379 if (oldStart >= 0) {
7380 if (ims.mChangedStart > oldStart) {
7381 ims.mChangedStart = oldStart;
7382 }
7383 if (ims.mChangedStart > oldEnd) {
7384 ims.mChangedStart = oldEnd;
7385 }
7386 }
7387 if (newStart >= 0) {
7388 if (ims.mChangedStart > newStart) {
7389 ims.mChangedStart = newStart;
7390 }
7391 if (ims.mChangedStart > newEnd) {
7392 ims.mChangedStart = newEnd;
7393 }
7394 }
7395 } else {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007396 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: "
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007397 + oldStart + "-" + oldEnd + ","
Gilles Debunnec62589c2012-04-12 14:50:23 -07007398 + newStart + "-" + newEnd + " " + what);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007399 ims.mContentChanged = true;
7400 }
7401 }
7402 }
Gilles Debunne6435a562011-08-04 21:22:30 -07007403
Gilles Debunne2d373a12012-04-20 15:32:19 -07007404 if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0 &&
7405 what instanceof SpellCheckSpan) {
Gilles Debunne69865bd2012-05-09 11:12:03 -07007406 mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
Gilles Debunne6435a562011-08-04 21:22:30 -07007407 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007408 }
7409
Gilles Debunne6435a562011-08-04 21:22:30 -07007410 /**
Romain Guydcc490f2010-02-24 17:59:35 -08007411 * @hide
7412 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007413 @Override
Romain Guya440b002010-02-24 15:57:54 -08007414 public void dispatchFinishTemporaryDetach() {
7415 mDispatchTemporaryDetach = true;
7416 super.dispatchFinishTemporaryDetach();
7417 mDispatchTemporaryDetach = false;
7418 }
7419
7420 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007421 public void onStartTemporaryDetach() {
Romain Guya440b002010-02-24 15:57:54 -08007422 super.onStartTemporaryDetach();
7423 // Only track when onStartTemporaryDetach() is called directly,
7424 // usually because this instance is an editable field in a list
7425 if (!mDispatchTemporaryDetach) mTemporaryDetach = true;
Gilles Debunne4b2274f2011-02-25 15:18:03 -08007426
Adam Powell057a5852012-05-11 10:28:38 -07007427 // Tell the editor that we are temporarily detached. It can use this to preserve
7428 // selection state as needed.
7429 if (mEditor != null) mEditor.mTemporaryDetach = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007430 }
Gilles Debunne3784a7f2011-07-15 13:49:38 -07007431
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007432 @Override
7433 public void onFinishTemporaryDetach() {
Romain Guya440b002010-02-24 15:57:54 -08007434 super.onFinishTemporaryDetach();
7435 // Only track when onStartTemporaryDetach() is called directly,
7436 // usually because this instance is an editable field in a list
7437 if (!mDispatchTemporaryDetach) mTemporaryDetach = false;
Adam Powell057a5852012-05-11 10:28:38 -07007438 if (mEditor != null) mEditor.mTemporaryDetach = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007439 }
Gilles Debunne3784a7f2011-07-15 13:49:38 -07007440
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007441 @Override
7442 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
7443 if (mTemporaryDetach) {
7444 // If we are temporarily in the detach state, then do nothing.
7445 super.onFocusChanged(focused, direction, previouslyFocusedRect);
7446 return;
7447 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007448
Gilles Debunne2d373a12012-04-20 15:32:19 -07007449 if (mEditor != null) mEditor.onFocusChanged(focused, direction);
Gilles Debunne03789e82010-09-07 19:07:17 -07007450
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007451 if (focused) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007452 if (mText instanceof Spannable) {
7453 Spannable sp = (Spannable) mText;
7454 MetaKeyKeyListener.resetMetaState(sp);
7455 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007456 }
7457
7458 startStopMarquee(focused);
7459
7460 if (mTransformation != null) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07007461 mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007462 }
7463
7464 super.onFocusChanged(focused, direction, previouslyFocusedRect);
7465 }
7466
7467 @Override
7468 public void onWindowFocusChanged(boolean hasWindowFocus) {
7469 super.onWindowFocusChanged(hasWindowFocus);
7470
Gilles Debunne2d373a12012-04-20 15:32:19 -07007471 if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007472
7473 startStopMarquee(hasWindowFocus);
7474 }
7475
Adam Powellba0a2c32010-09-28 17:41:23 -07007476 @Override
7477 protected void onVisibilityChanged(View changedView, int visibility) {
7478 super.onVisibilityChanged(changedView, visibility);
Gilles Debunne60e21862012-01-30 15:04:14 -08007479 if (mEditor != null && visibility != VISIBLE) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007480 mEditor.hideControllers();
Adam Powellba0a2c32010-09-28 17:41:23 -07007481 }
7482 }
7483
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007484 /**
7485 * Use {@link BaseInputConnection#removeComposingSpans
7486 * BaseInputConnection.removeComposingSpans()} to remove any IME composing
7487 * state from this text view.
7488 */
7489 public void clearComposingText() {
7490 if (mText instanceof Spannable) {
7491 BaseInputConnection.removeComposingSpans((Spannable)mText);
7492 }
7493 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07007494
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007495 @Override
7496 public void setSelected(boolean selected) {
7497 boolean wasSelected = isSelected();
7498
7499 super.setSelected(selected);
7500
7501 if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7502 if (selected) {
7503 startMarquee();
7504 } else {
7505 stopMarquee();
7506 }
7507 }
7508 }
7509
7510 @Override
7511 public boolean onTouchEvent(MotionEvent event) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07007512 final int action = event.getActionMasked();
Adam Powell965b9692010-10-21 18:44:32 -07007513
Gilles Debunne2d373a12012-04-20 15:32:19 -07007514 if (mEditor != null) mEditor.onTouchEvent(event);
Gilles Debunnebbb5d6e2010-06-21 16:21:51 -07007515
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007516 final boolean superResult = super.onTouchEvent(event);
7517
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007518 /*
7519 * Don't handle the release after a long press, because it will
7520 * move the selection away from whatever the menu action was
7521 * trying to affect.
7522 */
Gilles Debunne2d373a12012-04-20 15:32:19 -07007523 if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
7524 mEditor.mDiscardNextActionUp = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007525 return superResult;
7526 }
7527
Gilles Debunne70a63122011-09-01 13:27:33 -07007528 final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) &&
Gilles Debunne2d373a12012-04-20 15:32:19 -07007529 (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
Gilles Debunnec3e85a72011-01-21 08:46:06 -08007530
Gilles Debunne70a63122011-09-01 13:27:33 -07007531 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
Janos Levai042856c2010-10-15 02:53:58 +03007532 && mText instanceof Spannable && mLayout != null) {
Gilles Debunnef788a9f2010-07-22 10:17:23 -07007533 boolean handled = false;
7534
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07007535 if (mMovement != null) {
7536 handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
7537 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007538
Gilles Debunne60e21862012-01-30 15:04:14 -08007539 final boolean textIsSelectable = isTextSelectable();
7540 if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
Gilles Debunnef3895ed2010-12-21 12:53:58 -08007541 // The LinkMovementMethod which should handle taps on links has not been installed
Gilles Debunne70a63122011-09-01 13:27:33 -07007542 // on non editable text that support text selection.
7543 // We reproduce its behavior here to open links for these.
Gilles Debunnef3895ed2010-12-21 12:53:58 -08007544 ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
7545 getSelectionEnd(), ClickableSpan.class);
7546
Gilles Debunne822b8f02012-01-17 18:02:15 -08007547 if (links.length > 0) {
Gilles Debunnef3895ed2010-12-21 12:53:58 -08007548 links[0].onClick(this);
7549 handled = true;
7550 }
7551 }
7552
Gilles Debunne60e21862012-01-30 15:04:14 -08007553 if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
Gilles Debunne180bb1b2011-03-10 11:14:00 -08007554 // Show the IME, except when selecting in read-only text.
satok863fcd62011-06-21 17:38:02 +09007555 final InputMethodManager imm = InputMethodManager.peekInstance();
satoka67a3cf2011-09-07 17:14:03 +09007556 viewClicked(imm);
Gilles Debunne3473b2b2012-04-20 16:21:10 -07007557 if (!textIsSelectable && mEditor.mShowSoftInputOnFocus) {
Gilles Debunne180bb1b2011-03-10 11:14:00 -08007558 handled |= imm != null && imm.showSoftInput(this, 0);
Adam Powell879fb6b2010-09-20 11:23:56 -07007559 }
Gilles Debunnebbb5d6e2010-06-21 16:21:51 -07007560
Gilles Debunned88876a2012-03-16 17:34:04 -07007561 // The above condition ensures that the mEditor is not null
Gilles Debunne2d373a12012-04-20 15:32:19 -07007562 mEditor.onTouchUpEvent(event);
Gilles Debunne6435a562011-08-04 21:22:30 -07007563
7564 handled = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007565 }
7566
The Android Open Source Project4df24232009-03-05 14:34:35 -08007567 if (handled) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007568 return true;
7569 }
7570 }
7571
7572 return superResult;
7573 }
7574
Jeff Brown8f345672011-02-26 13:29:53 -08007575 @Override
7576 public boolean onGenericMotionEvent(MotionEvent event) {
7577 if (mMovement != null && mText instanceof Spannable && mLayout != null) {
7578 try {
7579 if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) {
7580 return true;
7581 }
7582 } catch (AbstractMethodError ex) {
7583 // onGenericMotionEvent was added to the MovementMethod interface in API 12.
7584 // Ignore its absence in case third party applications implemented the
7585 // interface directly.
7586 }
7587 }
7588 return super.onGenericMotionEvent(event);
7589 }
7590
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007591 /**
Gilles Debunne86b9c782010-11-11 10:43:48 -08007592 * @return True iff this TextView contains a text that can be edited, or if this is
7593 * a selectable TextView.
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007594 */
Gilles Debunned88876a2012-03-16 17:34:04 -07007595 boolean isTextEditable() {
Gilles Debunnef076eeb2010-11-29 11:32:53 -08007596 return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007597 }
7598
The Android Open Source Project4df24232009-03-05 14:34:35 -08007599 /**
7600 * Returns true, only while processing a touch gesture, if the initial
7601 * 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 -07007602 * its selection changed. Only valid while processing the touch gesture
Gilles Debunne053c4392012-03-15 15:35:26 -07007603 * of interest, in an editable text view.
The Android Open Source Project4df24232009-03-05 14:34:35 -08007604 */
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07007605 public boolean didTouchFocusSelect() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007606 return mEditor != null && mEditor.mTouchFocusSelected;
The Android Open Source Project4df24232009-03-05 14:34:35 -08007607 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07007608
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007609 @Override
7610 public void cancelLongPress() {
7611 super.cancelLongPress();
Gilles Debunne2d373a12012-04-20 15:32:19 -07007612 if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007613 }
Gilles Debunne70a63122011-09-01 13:27:33 -07007614
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007615 @Override
7616 public boolean onTrackballEvent(MotionEvent event) {
Gilles Debunne60e21862012-01-30 15:04:14 -08007617 if (mMovement != null && mText instanceof Spannable && mLayout != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007618 if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
7619 return true;
7620 }
7621 }
7622
7623 return super.onTrackballEvent(event);
7624 }
7625
7626 public void setScroller(Scroller s) {
7627 mScroller = s;
7628 }
7629
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007630 @Override
7631 protected float getLeftFadingEdgeStrength() {
Adam Powell282e3772011-08-30 16:51:11 -07007632 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
7633 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007634 if (mMarquee != null && !mMarquee.isStopped()) {
7635 final Marquee marquee = mMarquee;
Romain Guyc2303192009-04-03 17:37:18 -07007636 if (marquee.shouldDrawLeftFade()) {
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07007637 final float scroll = marquee.getScroll();
7638 return scroll / getHorizontalFadingEdgeLength();
Romain Guyc2303192009-04-03 17:37:18 -07007639 } else {
7640 return 0.0f;
7641 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007642 } else if (getLineCount() == 1) {
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07007643 final int layoutDirection = getLayoutDirection();
Fabrice Di Meglioc0053222011-06-13 12:16:51 -07007644 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07007645 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007646 case Gravity.LEFT:
7647 return 0.0f;
7648 case Gravity.RIGHT:
7649 return (mLayout.getLineRight(0) - (mRight - mLeft) -
7650 getCompoundPaddingLeft() - getCompoundPaddingRight() -
7651 mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
7652 case Gravity.CENTER_HORIZONTAL:
7653 return 0.0f;
7654 }
7655 }
7656 }
7657 return super.getLeftFadingEdgeStrength();
7658 }
7659
7660 @Override
7661 protected float getRightFadingEdgeStrength() {
Adam Powell282e3772011-08-30 16:51:11 -07007662 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
7663 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007664 if (mMarquee != null && !mMarquee.isStopped()) {
7665 final Marquee marquee = mMarquee;
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07007666 final float maxFadeScroll = marquee.getMaxFadeScroll();
7667 final float scroll = marquee.getScroll();
7668 return (maxFadeScroll - scroll) / getHorizontalFadingEdgeLength();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007669 } else if (getLineCount() == 1) {
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07007670 final int layoutDirection = getLayoutDirection();
Fabrice Di Meglioc0053222011-06-13 12:16:51 -07007671 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07007672 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007673 case Gravity.LEFT:
Romain Guy076dc9f2009-06-24 17:17:51 -07007674 final int textWidth = (mRight - mLeft) - getCompoundPaddingLeft() -
7675 getCompoundPaddingRight();
7676 final float lineWidth = mLayout.getLineWidth(0);
7677 return (lineWidth - textWidth) / getHorizontalFadingEdgeLength();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007678 case Gravity.RIGHT:
7679 return 0.0f;
7680 case Gravity.CENTER_HORIZONTAL:
Gilles Debunne44c14732010-10-19 11:56:59 -07007681 case Gravity.FILL_HORIZONTAL:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007682 return (mLayout.getLineWidth(0) - ((mRight - mLeft) -
7683 getCompoundPaddingLeft() - getCompoundPaddingRight())) /
7684 getHorizontalFadingEdgeLength();
7685 }
7686 }
7687 }
7688 return super.getRightFadingEdgeStrength();
7689 }
7690
7691 @Override
7692 protected int computeHorizontalScrollRange() {
Romain Guydac5f9f2010-07-08 11:40:54 -07007693 if (mLayout != null) {
7694 return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT ?
7695 (int) mLayout.getLineWidth(0) : mLayout.getWidth();
7696 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007697
7698 return super.computeHorizontalScrollRange();
7699 }
7700
7701 @Override
7702 protected int computeVerticalScrollRange() {
7703 if (mLayout != null)
7704 return mLayout.getHeight();
7705
7706 return super.computeVerticalScrollRange();
7707 }
7708
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07007709 @Override
7710 protected int computeVerticalScrollExtent() {
7711 return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
7712 }
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07007713
7714 @Override
Svetoslav Ganovea515ae2011-09-14 18:15:32 -07007715 public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
7716 super.findViewsWithText(outViews, searched, flags);
7717 if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
7718 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
7719 String searchedLowerCase = searched.toString().toLowerCase();
7720 String textLowerCase = mText.toString().toLowerCase();
7721 if (textLowerCase.contains(searchedLowerCase)) {
7722 outViews.add(this);
7723 }
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07007724 }
7725 }
7726
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007727 public enum BufferType {
7728 NORMAL, SPANNABLE, EDITABLE,
7729 }
7730
7731 /**
7732 * Returns the TextView_textColor attribute from the
7733 * Resources.StyledAttributes, if set, or the TextAppearance_textColor
7734 * from the TextView_textAppearance attribute, if TextView_textColor
7735 * was not set directly.
7736 */
7737 public static ColorStateList getTextColors(Context context, TypedArray attrs) {
7738 ColorStateList colors;
7739 colors = attrs.getColorStateList(com.android.internal.R.styleable.
7740 TextView_textColor);
7741
7742 if (colors == null) {
7743 int ap = attrs.getResourceId(com.android.internal.R.styleable.
7744 TextView_textAppearance, -1);
7745 if (ap != -1) {
7746 TypedArray appearance;
7747 appearance = context.obtainStyledAttributes(ap,
7748 com.android.internal.R.styleable.TextAppearance);
7749 colors = appearance.getColorStateList(com.android.internal.R.styleable.
7750 TextAppearance_textColor);
7751 appearance.recycle();
7752 }
7753 }
7754
7755 return colors;
7756 }
7757
7758 /**
7759 * Returns the default color from the TextView_textColor attribute
7760 * from the AttributeSet, if set, or the default color from the
7761 * TextAppearance_textColor from the TextView_textAppearance attribute,
7762 * if TextView_textColor was not set directly.
7763 */
7764 public static int getTextColor(Context context,
7765 TypedArray attrs,
7766 int def) {
7767 ColorStateList colors = getTextColors(context, attrs);
7768
7769 if (colors == null) {
7770 return def;
7771 } else {
7772 return colors.getDefaultColor();
7773 }
7774 }
7775
7776 @Override
7777 public boolean onKeyShortcut(int keyCode, KeyEvent event) {
Jeff Brownc1df9072010-12-21 16:38:50 -08007778 final int filteredMetaState = event.getMetaState() & ~KeyEvent.META_CTRL_MASK;
7779 if (KeyEvent.metaStateHasNoModifiers(filteredMetaState)) {
7780 switch (keyCode) {
7781 case KeyEvent.KEYCODE_A:
7782 if (canSelectText()) {
7783 return onTextContextMenuItem(ID_SELECT_ALL);
7784 }
7785 break;
7786 case KeyEvent.KEYCODE_X:
7787 if (canCut()) {
7788 return onTextContextMenuItem(ID_CUT);
7789 }
7790 break;
7791 case KeyEvent.KEYCODE_C:
7792 if (canCopy()) {
7793 return onTextContextMenuItem(ID_COPY);
7794 }
7795 break;
7796 case KeyEvent.KEYCODE_V:
7797 if (canPaste()) {
7798 return onTextContextMenuItem(ID_PASTE);
7799 }
7800 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007801 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007802 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007803 return super.onKeyShortcut(keyCode, event);
7804 }
7805
Gilles Debunnecbcb3452010-12-17 15:31:02 -08007806 /**
7807 * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
7808 * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
Gilles Debunne2d373a12012-04-20 15:32:19 -07007809 * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
7810 * sufficient.
Gilles Debunnecbcb3452010-12-17 15:31:02 -08007811 */
Gilles Debunnebaaace52010-10-01 15:47:13 -07007812 private boolean canSelectText() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007813 return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007814 }
7815
Gilles Debunnecbcb3452010-12-17 15:31:02 -08007816 /**
7817 * Test based on the <i>intrinsic</i> charateristics of the TextView.
7818 * The text must be spannable and the movement method must allow for arbitary selection.
Gilles Debunne2d373a12012-04-20 15:32:19 -07007819 *
Gilles Debunnecbcb3452010-12-17 15:31:02 -08007820 * See also {@link #canSelectText()}.
7821 */
Gilles Debunned88876a2012-03-16 17:34:04 -07007822 boolean textCanBeSelected() {
Gilles Debunne05336272010-07-09 20:13:45 -07007823 // prepareCursorController() relies on this method.
7824 // If you change this condition, make sure prepareCursorController is called anywhere
7825 // the value of this condition might be changed.
Gilles Debunnebb588da2011-07-11 18:26:19 -07007826 if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07007827 return isTextEditable() ||
7828 (isTextSelectable() && mText instanceof Spannable && isEnabled());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007829 }
7830
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07007831 /**
7832 * This is a temporary method. Future versions may support multi-locale text.
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09007833 * Caveat: This method may not return the latest text services locale, but this should be
7834 * acceptable and it's more important to make this method asynchronous.
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07007835 *
satok05f24702011-11-02 19:29:35 +09007836 * @return The locale that should be used for a word iterator and a spell checker
7837 * in this TextView, based on the current spell checker settings,
7838 * the current IME's locale, or the system default locale.
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07007839 * @hide
7840 */
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09007841 // TODO: Support multi-locale
7842 // TODO: Update the text services locale immediately after the keyboard locale is switched
7843 // by catching intent of keyboard switch event
satok05f24702011-11-02 19:29:35 +09007844 public Locale getTextServicesLocale() {
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09007845 if (mCurrentTextServicesLocaleCache == null) {
7846 // If there is no cached text services locale, just return the default locale.
7847 mCurrentTextServicesLocaleCache = Locale.getDefault();
7848 }
7849 // Start fetching the text services locale asynchronously.
7850 updateTextServicesLocaleAsync();
7851 return mCurrentTextServicesLocaleCache;
7852 }
7853
7854 private void updateTextServicesLocaleAsync() {
7855 AsyncTask.execute(new Runnable() {
7856 @Override
7857 public void run() {
7858 if (mCurrentTextServicesLocaleLock.tryLock()) {
7859 try {
7860 updateTextServicesLocaleLocked();
7861 } finally {
7862 mCurrentTextServicesLocaleLock.unlock();
7863 }
7864 }
7865 }
7866 });
7867 }
7868
7869 private void updateTextServicesLocaleLocked() {
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07007870 Locale locale = Locale.getDefault();
satok05f24702011-11-02 19:29:35 +09007871 final TextServicesManager textServicesManager = (TextServicesManager)
7872 mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
7873 final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
7874 if (subtype != null) {
satokf927e172012-05-24 16:52:54 +09007875 locale = SpellCheckerSubtype.constructLocaleFromString(subtype.getLocale());
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07007876 }
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09007877 mCurrentTextServicesLocaleCache = locale;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07007878 }
7879
7880 void onLocaleChanged() {
7881 // Will be re-created on demand in getWordIterator with the proper new locale
Gilles Debunne2d373a12012-04-20 15:32:19 -07007882 mEditor.mWordIterator = null;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07007883 }
7884
7885 /**
Gilles Debunned88876a2012-03-16 17:34:04 -07007886 * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
7887 * Made available to achieve a consistent behavior.
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07007888 * @hide
7889 */
7890 public WordIterator getWordIterator() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007891 if (mEditor != null) {
7892 return mEditor.getWordIterator();
Gilles Debunned88876a2012-03-16 17:34:04 -07007893 } else {
7894 return null;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07007895 }
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07007896 }
Gilles Debunnedf4ee432010-08-25 19:13:48 -07007897
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007898 @Override
Svetoslav Ganov736c2752011-04-22 18:30:36 -07007899 public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
Svetoslav Ganov887e1a12011-04-29 15:09:28 -07007900 super.onPopulateAccessibilityEvent(event);
7901
Svetoslav Ganov1d1e1102010-11-16 16:44:03 -08007902 final boolean isPassword = hasPasswordTransformationMethod();
alanv7d624192012-05-21 14:23:17 -07007903 if (!isPassword || shouldSpeakPasswordsForAccessibility()) {
7904 final CharSequence text = getTextForAccessibility();
Svetoslav Ganovd37848a2011-09-20 14:03:55 -07007905 if (!TextUtils.isEmpty(text)) {
svetoslavganov75986cf2009-05-14 22:28:01 -07007906 event.getText().add(text);
7907 }
svetoslavganov75986cf2009-05-14 22:28:01 -07007908 }
svetoslavganov75986cf2009-05-14 22:28:01 -07007909 }
7910
alanv7d624192012-05-21 14:23:17 -07007911 /**
7912 * @return true if the user has explicitly allowed accessibility services
7913 * to speak passwords.
7914 */
7915 private boolean shouldSpeakPasswordsForAccessibility() {
7916 return (Settings.Secure.getInt(mContext.getContentResolver(),
7917 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0) == 1);
7918 }
7919
Svetoslav Ganov30401322011-05-12 18:53:45 -07007920 @Override
7921 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
7922 super.onInitializeAccessibilityEvent(event);
7923
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08007924 event.setClassName(TextView.class.getName());
Svetoslav Ganov30401322011-05-12 18:53:45 -07007925 final boolean isPassword = hasPasswordTransformationMethod();
7926 event.setPassword(isPassword);
Svetoslav Ganova0156172011-06-26 17:55:44 -07007927
7928 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
7929 event.setFromIndex(Selection.getSelectionStart(mText));
7930 event.setToIndex(Selection.getSelectionEnd(mText));
7931 event.setItemCount(mText.length());
7932 }
Svetoslav Ganov30401322011-05-12 18:53:45 -07007933 }
7934
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07007935 @Override
7936 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
7937 super.onInitializeAccessibilityNodeInfo(info);
7938
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08007939 info.setClassName(TextView.class.getName());
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07007940 final boolean isPassword = hasPasswordTransformationMethod();
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08007941 info.setPassword(isPassword);
7942
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07007943 if (!isPassword) {
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07007944 info.setText(getTextForAccessibility());
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07007945 }
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07007946
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07007947 if (TextUtils.isEmpty(getContentDescription()) && !TextUtils.isEmpty(mText)) {
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07007948 info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
7949 info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
7950 info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
7951 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
7952 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
7953 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
7954 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
7955 }
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07007956 }
7957
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07007958 @Override
7959 public void sendAccessibilityEvent(int eventType) {
7960 // Do not send scroll events since first they are not interesting for
7961 // accessibility and second such events a generated too frequently.
7962 // For details see the implementation of bringTextIntoView().
7963 if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
7964 return;
7965 }
7966 super.sendAccessibilityEvent(eventType);
7967 }
7968
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07007969 /**
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07007970 * Gets the text reported for accessibility purposes.
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07007971 *
7972 * @return The accessibility text.
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07007973 *
7974 * @hide
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07007975 */
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07007976 public CharSequence getTextForAccessibility() {
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07007977 CharSequence text = getText();
7978 if (TextUtils.isEmpty(text)) {
7979 text = getHint();
7980 }
7981 return text;
7982 }
7983
svetoslavganov75986cf2009-05-14 22:28:01 -07007984 void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
7985 int fromIndex, int removedCount, int addedCount) {
7986 AccessibilityEvent event =
7987 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
7988 event.setFromIndex(fromIndex);
7989 event.setRemovedCount(removedCount);
7990 event.setAddedCount(addedCount);
7991 event.setBeforeText(beforeText);
7992 sendAccessibilityEventUnchecked(event);
7993 }
7994
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007995 /**
7996 * Returns whether this text view is a current input method target. The
7997 * default implementation just checks with {@link InputMethodManager}.
7998 */
7999 public boolean isInputMethodTarget() {
8000 InputMethodManager imm = InputMethodManager.peekInstance();
8001 return imm != null && imm.isActive(this);
8002 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07008003
Gilles Debunned88876a2012-03-16 17:34:04 -07008004 static final int ID_SELECT_ALL = android.R.id.selectAll;
8005 static final int ID_CUT = android.R.id.cut;
8006 static final int ID_COPY = android.R.id.copy;
8007 static final int ID_PASTE = android.R.id.paste;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008008
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008009 /**
8010 * Called when a context menu option for the text view is selected. Currently
Gilles Debunne07194e52011-11-02 14:18:44 -07008011 * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
8012 * {@link android.R.id#copy} or {@link android.R.id#paste}.
Gilles Debunnec59269f2011-04-22 11:46:09 -07008013 *
8014 * @return true if the context menu item action was performed.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008015 */
8016 public boolean onTextContextMenuItem(int id) {
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07008017 int min = 0;
8018 int max = mText.length();
Gilles Debunne64e54a62010-09-07 19:07:17 -07008019
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07008020 if (isFocused()) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07008021 final int selStart = getSelectionStart();
8022 final int selEnd = getSelectionEnd();
8023
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07008024 min = Math.max(0, Math.min(selStart, selEnd));
8025 max = Math.max(0, Math.max(selStart, selEnd));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008026 }
8027
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008028 switch (id) {
Jeff Brownc1df9072010-12-21 16:38:50 -08008029 case ID_SELECT_ALL:
Gilles Debunne299733e2011-02-07 17:11:41 -08008030 // This does not enter text selection mode. Text is highlighted, so that it can be
Gilles Debunnec59269f2011-04-22 11:46:09 -07008031 // bulk edited, like selectAllOnFocus does. Returns true even if text is empty.
Gilles Debunned88876a2012-03-16 17:34:04 -07008032 selectAllText();
Jeff Brownc1df9072010-12-21 16:38:50 -08008033 return true;
8034
8035 case ID_PASTE:
8036 paste(min, max);
8037 return true;
8038
8039 case ID_CUT:
Gilles Debunnecf68fee2011-09-29 10:55:36 -07008040 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
Gilles Debunne39ba6d92011-11-09 05:26:26 +01008041 deleteText_internal(min, max);
Jeff Brownc1df9072010-12-21 16:38:50 -08008042 stopSelectionActionMode();
8043 return true;
8044
8045 case ID_COPY:
Gilles Debunnecf68fee2011-09-29 10:55:36 -07008046 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
Jeff Brownc1df9072010-12-21 16:38:50 -08008047 stopSelectionActionMode();
8048 return true;
8049 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008050 return false;
8051 }
8052
Gilles Debunned88876a2012-03-16 17:34:04 -07008053 CharSequence getTransformedText(int start, int end) {
Gilles Debunnecf68fee2011-09-29 10:55:36 -07008054 return removeSuggestionSpans(mTransformed.subSequence(start, end));
8055 }
8056
Gilles Debunnee15b3582010-06-16 15:17:21 -07008057 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008058 public boolean performLongClick() {
Gilles Debunnee28454a2011-09-07 18:03:44 -07008059 boolean handled = false;
Gilles Debunnee28454a2011-09-07 18:03:44 -07008060
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008061 if (super.performLongClick()) {
Gilles Debunnee28454a2011-09-07 18:03:44 -07008062 handled = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008063 }
Gilles Debunnef170a342010-11-11 11:08:59 -08008064
Gilles Debunned88876a2012-03-16 17:34:04 -07008065 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008066 handled |= mEditor.performLongClick(handled);
Gilles Debunnee28454a2011-09-07 18:03:44 -07008067 }
8068
Gilles Debunne9f102ca2012-02-28 11:15:54 -08008069 if (handled) {
Gilles Debunnee28454a2011-09-07 18:03:44 -07008070 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
Gilles Debunne2d373a12012-04-20 15:32:19 -07008071 if (mEditor != null) mEditor.mDiscardNextActionUp = true;
Gilles Debunnef788a9f2010-07-22 10:17:23 -07008072 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008073
Gilles Debunne299733e2011-02-07 17:11:41 -08008074 return handled;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008075 }
8076
Gilles Debunne60e21862012-01-30 15:04:14 -08008077 @Override
8078 protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
8079 super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
Gilles Debunne6382ade2012-02-29 15:22:32 -08008080 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008081 mEditor.onScrollChanged();
Gilles Debunne60e21862012-01-30 15:04:14 -08008082 }
8083 }
8084
8085 /**
Gilles Debunne60e21862012-01-30 15:04:14 -08008086 * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
8087 * by the IME or by the spell checker as the user types. This is done by adding
8088 * {@link SuggestionSpan}s to the text.
8089 *
8090 * When suggestions are enabled (default), this list of suggestions will be displayed when the
8091 * user asks for them on these parts of the text. This value depends on the inputType of this
8092 * TextView.
8093 *
8094 * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
8095 *
8096 * In addition, the type variation must be one of
8097 * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
8098 * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
8099 * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
8100 * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
8101 * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
8102 *
8103 * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
8104 *
8105 * @return true if the suggestions popup window is enabled, based on the inputType.
8106 */
8107 public boolean isSuggestionsEnabled() {
8108 if (mEditor == null) return false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07008109 if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
8110 return false;
8111 }
8112 if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
Gilles Debunne60e21862012-01-30 15:04:14 -08008113
Gilles Debunne2d373a12012-04-20 15:32:19 -07008114 final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
Gilles Debunne60e21862012-01-30 15:04:14 -08008115 return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL ||
8116 variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT ||
8117 variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE ||
8118 variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE ||
8119 variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
8120 }
8121
8122 /**
8123 * If provided, this ActionMode.Callback will be used to create the ActionMode when text
8124 * selection is initiated in this View.
8125 *
8126 * The standard implementation populates the menu with a subset of Select All, Cut, Copy and
8127 * Paste actions, depending on what this View supports.
8128 *
8129 * A custom implementation can add new entries in the default menu in its
8130 * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, Menu)} method. The
8131 * default actions can also be removed from the menu using {@link Menu#removeItem(int)} and
8132 * passing {@link android.R.id#selectAll}, {@link android.R.id#cut}, {@link android.R.id#copy}
8133 * or {@link android.R.id#paste} ids as parameters.
8134 *
8135 * Returning false from
8136 * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, Menu)} will prevent
8137 * the action mode from being started.
8138 *
8139 * Action click events should be handled by the custom implementation of
8140 * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, MenuItem)}.
8141 *
8142 * Note that text selection mode is not started when a TextView receives focus and the
8143 * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
8144 * that case, to allow for quick replacement.
8145 */
8146 public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07008147 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07008148 mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
Gilles Debunne60e21862012-01-30 15:04:14 -08008149 }
8150
8151 /**
8152 * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
8153 *
8154 * @return The current custom selection callback.
8155 */
8156 public ActionMode.Callback getCustomSelectionActionModeCallback() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008157 return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
Gilles Debunne60e21862012-01-30 15:04:14 -08008158 }
8159
8160 /**
Gilles Debunne60e21862012-01-30 15:04:14 -08008161 * @hide
8162 */
8163 protected void stopSelectionActionMode() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008164 mEditor.stopSelectionActionMode();
Gilles Debunned88876a2012-03-16 17:34:04 -07008165 }
8166
8167 boolean canCut() {
8168 if (hasPasswordTransformationMethod()) {
8169 return false;
Gilles Debunne60e21862012-01-30 15:04:14 -08008170 }
Gilles Debunned88876a2012-03-16 17:34:04 -07008171
Gilles Debunne2d373a12012-04-20 15:32:19 -07008172 if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null &&
8173 mEditor.mKeyListener != null) {
Gilles Debunned88876a2012-03-16 17:34:04 -07008174 return true;
8175 }
8176
8177 return false;
8178 }
8179
8180 boolean canCopy() {
8181 if (hasPasswordTransformationMethod()) {
8182 return false;
8183 }
8184
8185 if (mText.length() > 0 && hasSelection()) {
8186 return true;
8187 }
8188
8189 return false;
8190 }
8191
8192 boolean canPaste() {
8193 return (mText instanceof Editable &&
Gilles Debunne2d373a12012-04-20 15:32:19 -07008194 mEditor != null && mEditor.mKeyListener != null &&
Gilles Debunned88876a2012-03-16 17:34:04 -07008195 getSelectionStart() >= 0 &&
8196 getSelectionEnd() >= 0 &&
8197 ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
8198 hasPrimaryClip());
8199 }
8200
8201 boolean selectAllText() {
8202 final int length = mText.length();
8203 Selection.setSelection((Spannable) mText, 0, length);
8204 return length > 0;
8205 }
8206
8207 /**
8208 * Prepare text so that there are not zero or two spaces at beginning and end of region defined
8209 * by [min, max] when replacing this region by paste.
8210 * Note that if there were two spaces (or more) at that position before, they are kept. We just
8211 * make sure we do not add an extra one from the paste content.
8212 */
8213 long prepareSpacesAroundPaste(int min, int max, CharSequence paste) {
8214 if (paste.length() > 0) {
8215 if (min > 0) {
8216 final char charBefore = mTransformed.charAt(min - 1);
8217 final char charAfter = paste.charAt(0);
8218
8219 if (Character.isSpaceChar(charBefore) && Character.isSpaceChar(charAfter)) {
8220 // Two spaces at beginning of paste: remove one
8221 final int originalLength = mText.length();
8222 deleteText_internal(min - 1, min);
8223 // Due to filters, there is no guarantee that exactly one character was
8224 // removed: count instead.
8225 final int delta = mText.length() - originalLength;
8226 min += delta;
8227 max += delta;
8228 } else if (!Character.isSpaceChar(charBefore) && charBefore != '\n' &&
8229 !Character.isSpaceChar(charAfter) && charAfter != '\n') {
8230 // No space at beginning of paste: add one
8231 final int originalLength = mText.length();
8232 replaceText_internal(min, min, " ");
8233 // Taking possible filters into account as above.
8234 final int delta = mText.length() - originalLength;
8235 min += delta;
8236 max += delta;
8237 }
8238 }
8239
8240 if (max < mText.length()) {
8241 final char charBefore = paste.charAt(paste.length() - 1);
8242 final char charAfter = mTransformed.charAt(max);
8243
8244 if (Character.isSpaceChar(charBefore) && Character.isSpaceChar(charAfter)) {
8245 // Two spaces at end of paste: remove one
8246 deleteText_internal(max, max + 1);
8247 } else if (!Character.isSpaceChar(charBefore) && charBefore != '\n' &&
8248 !Character.isSpaceChar(charAfter) && charAfter != '\n') {
8249 // No space at end of paste: add one
8250 replaceText_internal(max, max, " ");
8251 }
8252 }
8253 }
8254
8255 return TextUtils.packRangeInLong(min, max);
Gilles Debunne60e21862012-01-30 15:04:14 -08008256 }
8257
8258 /**
8259 * Paste clipboard content between min and max positions.
8260 */
8261 private void paste(int min, int max) {
8262 ClipboardManager clipboard =
8263 (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
8264 ClipData clip = clipboard.getPrimaryClip();
8265 if (clip != null) {
8266 boolean didFirst = false;
8267 for (int i=0; i<clip.getItemCount(); i++) {
Dianne Hackbornacb69bb2012-04-13 15:36:06 -07008268 CharSequence paste = clip.getItemAt(i).coerceToStyledText(getContext());
Gilles Debunne60e21862012-01-30 15:04:14 -08008269 if (paste != null) {
8270 if (!didFirst) {
8271 long minMax = prepareSpacesAroundPaste(min, max, paste);
Gilles Debunne6c488de2012-03-01 16:20:35 -08008272 min = TextUtils.unpackRangeStartFromLong(minMax);
8273 max = TextUtils.unpackRangeEndFromLong(minMax);
Gilles Debunne60e21862012-01-30 15:04:14 -08008274 Selection.setSelection((Spannable) mText, max);
8275 ((Editable) mText).replace(min, max, paste);
8276 didFirst = true;
8277 } else {
8278 ((Editable) mText).insert(getSelectionEnd(), "\n");
8279 ((Editable) mText).insert(getSelectionEnd(), paste);
8280 }
8281 }
8282 }
8283 stopSelectionActionMode();
8284 LAST_CUT_OR_COPY_TIME = 0;
8285 }
8286 }
8287
8288 private void setPrimaryClip(ClipData clip) {
8289 ClipboardManager clipboard = (ClipboardManager) getContext().
8290 getSystemService(Context.CLIPBOARD_SERVICE);
8291 clipboard.setPrimaryClip(clip);
8292 LAST_CUT_OR_COPY_TIME = SystemClock.uptimeMillis();
8293 }
8294
Gilles Debunne60e21862012-01-30 15:04:14 -08008295 /**
8296 * Get the character offset closest to the specified absolute position. A typical use case is to
8297 * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
8298 *
8299 * @param x The horizontal absolute position of a point on screen
8300 * @param y The vertical absolute position of a point on screen
8301 * @return the character offset for the character whose position is closest to the specified
8302 * position. Returns -1 if there is no layout.
8303 */
8304 public int getOffsetForPosition(float x, float y) {
8305 if (getLayout() == null) return -1;
8306 final int line = getLineAtCoordinate(y);
8307 final int offset = getOffsetAtCoordinate(line, x);
8308 return offset;
8309 }
8310
Gilles Debunned88876a2012-03-16 17:34:04 -07008311 float convertToLocalHorizontalCoordinate(float x) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008312 x -= getTotalPaddingLeft();
8313 // Clamp the position to inside of the view.
8314 x = Math.max(0.0f, x);
8315 x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
8316 x += getScrollX();
8317 return x;
8318 }
8319
Gilles Debunned88876a2012-03-16 17:34:04 -07008320 int getLineAtCoordinate(float y) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008321 y -= getTotalPaddingTop();
8322 // Clamp the position to inside of the view.
8323 y = Math.max(0.0f, y);
8324 y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
8325 y += getScrollY();
8326 return getLayout().getLineForVertical((int) y);
8327 }
8328
8329 private int getOffsetAtCoordinate(int line, float x) {
8330 x = convertToLocalHorizontalCoordinate(x);
8331 return getLayout().getOffsetForHorizontal(line, x);
8332 }
8333
Gilles Debunne60e21862012-01-30 15:04:14 -08008334 @Override
8335 public boolean onDragEvent(DragEvent event) {
8336 switch (event.getAction()) {
8337 case DragEvent.ACTION_DRAG_STARTED:
Gilles Debunne2d373a12012-04-20 15:32:19 -07008338 return mEditor != null && mEditor.hasInsertionController();
Gilles Debunne60e21862012-01-30 15:04:14 -08008339
8340 case DragEvent.ACTION_DRAG_ENTERED:
8341 TextView.this.requestFocus();
8342 return true;
8343
8344 case DragEvent.ACTION_DRAG_LOCATION:
8345 final int offset = getOffsetForPosition(event.getX(), event.getY());
8346 Selection.setSelection((Spannable)mText, offset);
8347 return true;
8348
8349 case DragEvent.ACTION_DROP:
Gilles Debunne2d373a12012-04-20 15:32:19 -07008350 if (mEditor != null) mEditor.onDrop(event);
Gilles Debunne60e21862012-01-30 15:04:14 -08008351 return true;
8352
8353 case DragEvent.ACTION_DRAG_ENDED:
8354 case DragEvent.ACTION_DRAG_EXITED:
8355 default:
8356 return true;
8357 }
8358 }
8359
Gilles Debunne60e21862012-01-30 15:04:14 -08008360 boolean isInBatchEditMode() {
8361 if (mEditor == null) return false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07008362 final Editor.InputMethodState ims = mEditor.mInputMethodState;
Gilles Debunne60e21862012-01-30 15:04:14 -08008363 if (ims != null) {
8364 return ims.mBatchEditNesting > 0;
8365 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07008366 return mEditor.mInBatchEditControllers;
Gilles Debunne60e21862012-01-30 15:04:14 -08008367 }
8368
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008369 TextDirectionHeuristic getTextDirectionHeuristic() {
Gilles Debunne60e21862012-01-30 15:04:14 -08008370 if (hasPasswordTransformationMethod()) {
Fabrice Di Meglio8701bb92012-11-14 19:57:11 -08008371 // passwords fields should be LTR
8372 return TextDirectionHeuristics.LTR;
Gilles Debunne60e21862012-01-30 15:04:14 -08008373 }
8374
8375 // Always need to resolve layout direction first
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07008376 final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
Gilles Debunne60e21862012-01-30 15:04:14 -08008377
8378 // Now, we can select the heuristic
Fabrice Di Meglio97e146c2012-09-23 15:45:16 -07008379 switch (getTextDirection()) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008380 default:
8381 case TEXT_DIRECTION_FIRST_STRONG:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008382 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
Gilles Debunne60e21862012-01-30 15:04:14 -08008383 TextDirectionHeuristics.FIRSTSTRONG_LTR);
Gilles Debunne60e21862012-01-30 15:04:14 -08008384 case TEXT_DIRECTION_ANY_RTL:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008385 return TextDirectionHeuristics.ANYRTL_LTR;
Gilles Debunne60e21862012-01-30 15:04:14 -08008386 case TEXT_DIRECTION_LTR:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008387 return TextDirectionHeuristics.LTR;
Gilles Debunne60e21862012-01-30 15:04:14 -08008388 case TEXT_DIRECTION_RTL:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008389 return TextDirectionHeuristics.RTL;
Gilles Debunne60e21862012-01-30 15:04:14 -08008390 case TEXT_DIRECTION_LOCALE:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008391 return TextDirectionHeuristics.LOCALE;
Gilles Debunne60e21862012-01-30 15:04:14 -08008392 }
8393 }
8394
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008395 /**
8396 * @hide
8397 */
Fabrice Di Megliob03b4342012-06-04 12:55:30 -07008398 @Override
8399 public void onResolveDrawables(int layoutDirection) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008400 // No need to resolve twice
Fabrice Di Meglio1957d282012-10-25 17:42:39 -07008401 if (mLastLayoutDirection == layoutDirection) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008402 return;
8403 }
Fabrice Di Meglio1957d282012-10-25 17:42:39 -07008404 mLastLayoutDirection = layoutDirection;
Gilles Debunne60e21862012-01-30 15:04:14 -08008405
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -08008406 // Resolve drawables
8407 if (mDrawables != null) {
8408 mDrawables.resolveWithLayoutDirection(layoutDirection);
Fabrice Di Megliob03b4342012-06-04 12:55:30 -07008409 }
8410 }
8411
Fabrice Di Meglio84ebb352012-10-11 16:27:37 -07008412 /**
8413 * @hide
8414 */
Gilles Debunne60e21862012-01-30 15:04:14 -08008415 protected void resetResolvedDrawables() {
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -08008416 super.resetResolvedDrawables();
Fabrice Di Meglio1957d282012-10-25 17:42:39 -07008417 mLastLayoutDirection = -1;
Gilles Debunne60e21862012-01-30 15:04:14 -08008418 }
8419
8420 /**
8421 * @hide
8422 */
8423 protected void viewClicked(InputMethodManager imm) {
8424 if (imm != null) {
8425 imm.viewClicked(this);
8426 }
8427 }
8428
8429 /**
8430 * Deletes the range of text [start, end[.
8431 * @hide
8432 */
8433 protected void deleteText_internal(int start, int end) {
8434 ((Editable) mText).delete(start, end);
8435 }
8436
8437 /**
8438 * Replaces the range of text [start, end[ by replacement text
8439 * @hide
8440 */
8441 protected void replaceText_internal(int start, int end, CharSequence text) {
8442 ((Editable) mText).replace(start, end, text);
8443 }
8444
8445 /**
8446 * Sets a span on the specified range of text
8447 * @hide
8448 */
8449 protected void setSpan_internal(Object span, int start, int end, int flags) {
8450 ((Editable) mText).setSpan(span, start, end, flags);
8451 }
8452
8453 /**
8454 * Moves the cursor to the specified offset position in text
8455 * @hide
8456 */
8457 protected void setCursorPosition_internal(int start, int end) {
8458 Selection.setSelection(((Editable) mText), start, end);
8459 }
8460
8461 /**
8462 * An Editor should be created as soon as any of the editable-specific fields (grouped
8463 * inside the Editor object) is assigned to a non-default value.
8464 * This method will create the Editor if needed.
8465 *
8466 * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
8467 * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
8468 * Editor for backward compatibility, as soon as one of these fields is assigned.
8469 *
8470 * Also note that for performance reasons, the mEditor is created when needed, but not
8471 * reset when no more edit-specific fields are needed.
8472 */
Gilles Debunne5fae9962012-05-08 14:53:20 -07008473 private void createEditorIfNeeded() {
Gilles Debunne60e21862012-01-30 15:04:14 -08008474 if (mEditor == null) {
Gilles Debunned88876a2012-03-16 17:34:04 -07008475 mEditor = new Editor(this);
Gilles Debunne60e21862012-01-30 15:04:14 -08008476 }
8477 }
8478
Gilles Debunne60e21862012-01-30 15:04:14 -08008479 /**
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008480 * @hide
8481 */
8482 @Override
8483 public CharSequence getIterableTextForAccessibility() {
Svetoslav Ganov2f4bf522012-09-06 19:07:44 -07008484 if (!TextUtils.isEmpty(mText)) {
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008485 if (!(mText instanceof Spannable)) {
8486 setText(mText, BufferType.SPANNABLE);
8487 }
8488 return mText;
8489 }
8490 return super.getIterableTextForAccessibility();
8491 }
8492
8493 /**
8494 * @hide
8495 */
8496 @Override
8497 public TextSegmentIterator getIteratorForGranularity(int granularity) {
8498 switch (granularity) {
8499 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
8500 Spannable text = (Spannable) getIterableTextForAccessibility();
8501 if (!TextUtils.isEmpty(text) && getLayout() != null) {
8502 AccessibilityIterators.LineTextSegmentIterator iterator =
8503 AccessibilityIterators.LineTextSegmentIterator.getInstance();
8504 iterator.initialize(text, getLayout());
8505 return iterator;
8506 }
8507 } break;
8508 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
8509 Spannable text = (Spannable) getIterableTextForAccessibility();
8510 if (!TextUtils.isEmpty(text) && getLayout() != null) {
8511 AccessibilityIterators.PageTextSegmentIterator iterator =
8512 AccessibilityIterators.PageTextSegmentIterator.getInstance();
8513 iterator.initialize(this);
8514 return iterator;
8515 }
8516 } break;
8517 }
8518 return super.getIteratorForGranularity(granularity);
8519 }
8520
8521 /**
8522 * @hide
8523 */
8524 @Override
8525 public int getAccessibilityCursorPosition() {
8526 if (TextUtils.isEmpty(getContentDescription())) {
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -07008527 final int selectionEnd = getSelectionEnd();
8528 if (selectionEnd >= 0) {
8529 return selectionEnd;
8530 }
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008531 }
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -07008532 return super.getAccessibilityCursorPosition();
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008533 }
8534
8535 /**
8536 * @hide
8537 */
8538 @Override
8539 public void setAccessibilityCursorPosition(int index) {
8540 if (getAccessibilityCursorPosition() == index) {
8541 return;
8542 }
8543 if (TextUtils.isEmpty(getContentDescription())) {
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -07008544 if (index >= 0 && index <= mText.length()) {
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008545 Selection.setSelection((Spannable) mText, index);
8546 } else {
8547 Selection.removeSelection((Spannable) mText);
8548 }
8549 } else {
8550 super.setAccessibilityCursorPosition(index);
8551 }
8552 }
8553
8554 /**
Gilles Debunne60e21862012-01-30 15:04:14 -08008555 * User interface state that is stored by TextView for implementing
8556 * {@link View#onSaveInstanceState}.
8557 */
8558 public static class SavedState extends BaseSavedState {
8559 int selStart;
8560 int selEnd;
8561 CharSequence text;
8562 boolean frozenWithFocus;
8563 CharSequence error;
8564
8565 SavedState(Parcelable superState) {
8566 super(superState);
8567 }
8568
8569 @Override
8570 public void writeToParcel(Parcel out, int flags) {
8571 super.writeToParcel(out, flags);
8572 out.writeInt(selStart);
8573 out.writeInt(selEnd);
8574 out.writeInt(frozenWithFocus ? 1 : 0);
8575 TextUtils.writeToParcel(text, out, flags);
8576
8577 if (error == null) {
8578 out.writeInt(0);
8579 } else {
8580 out.writeInt(1);
8581 TextUtils.writeToParcel(error, out, flags);
8582 }
8583 }
8584
8585 @Override
8586 public String toString() {
8587 String str = "TextView.SavedState{"
8588 + Integer.toHexString(System.identityHashCode(this))
8589 + " start=" + selStart + " end=" + selEnd;
8590 if (text != null) {
8591 str += " text=" + text;
8592 }
8593 return str + "}";
8594 }
8595
8596 @SuppressWarnings("hiding")
8597 public static final Parcelable.Creator<SavedState> CREATOR
8598 = new Parcelable.Creator<SavedState>() {
8599 public SavedState createFromParcel(Parcel in) {
8600 return new SavedState(in);
8601 }
8602
8603 public SavedState[] newArray(int size) {
8604 return new SavedState[size];
8605 }
8606 };
8607
8608 private SavedState(Parcel in) {
8609 super(in);
8610 selStart = in.readInt();
8611 selEnd = in.readInt();
8612 frozenWithFocus = (in.readInt() != 0);
8613 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
8614
8615 if (in.readInt() != 0) {
8616 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
8617 }
8618 }
8619 }
8620
8621 private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
8622 private char[] mChars;
8623 private int mStart, mLength;
8624
8625 public CharWrapper(char[] chars, int start, int len) {
8626 mChars = chars;
8627 mStart = start;
8628 mLength = len;
8629 }
8630
8631 /* package */ void set(char[] chars, int start, int len) {
8632 mChars = chars;
8633 mStart = start;
8634 mLength = len;
8635 }
8636
8637 public int length() {
8638 return mLength;
8639 }
8640
8641 public char charAt(int off) {
8642 return mChars[off + mStart];
8643 }
8644
8645 @Override
8646 public String toString() {
8647 return new String(mChars, mStart, mLength);
8648 }
8649
8650 public CharSequence subSequence(int start, int end) {
8651 if (start < 0 || end < 0 || start > mLength || end > mLength) {
8652 throw new IndexOutOfBoundsException(start + ", " + end);
8653 }
8654
8655 return new String(mChars, start + mStart, end - start);
8656 }
8657
8658 public void getChars(int start, int end, char[] buf, int off) {
8659 if (start < 0 || end < 0 || start > mLength || end > mLength) {
8660 throw new IndexOutOfBoundsException(start + ", " + end);
8661 }
8662
8663 System.arraycopy(mChars, start + mStart, buf, off, end - start);
8664 }
8665
8666 public void drawText(Canvas c, int start, int end,
8667 float x, float y, Paint p) {
8668 c.drawText(mChars, start + mStart, end - start, x, y, p);
8669 }
8670
8671 public void drawTextRun(Canvas c, int start, int end,
8672 int contextStart, int contextEnd, float x, float y, int flags, Paint p) {
8673 int count = end - start;
8674 int contextCount = contextEnd - contextStart;
8675 c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
8676 contextCount, x, y, flags, p);
8677 }
8678
8679 public float measureText(int start, int end, Paint p) {
8680 return p.measureText(mChars, start + mStart, end - start);
8681 }
8682
8683 public int getTextWidths(int start, int end, float[] widths, Paint p) {
8684 return p.getTextWidths(mChars, start + mStart, end - start, widths);
8685 }
8686
8687 public float getTextRunAdvances(int start, int end, int contextStart,
8688 int contextEnd, int flags, float[] advances, int advancesIndex,
8689 Paint p) {
8690 int count = end - start;
8691 int contextCount = contextEnd - contextStart;
8692 return p.getTextRunAdvances(mChars, start + mStart, count,
8693 contextStart + mStart, contextCount, flags, advances,
8694 advancesIndex);
8695 }
8696
8697 public float getTextRunAdvances(int start, int end, int contextStart,
8698 int contextEnd, int flags, float[] advances, int advancesIndex,
8699 Paint p, int reserved) {
8700 int count = end - start;
8701 int contextCount = contextEnd - contextStart;
8702 return p.getTextRunAdvances(mChars, start + mStart, count,
8703 contextStart + mStart, contextCount, flags, advances,
8704 advancesIndex, reserved);
8705 }
8706
8707 public int getTextRunCursor(int contextStart, int contextEnd, int flags,
8708 int offset, int cursorOpt, Paint p) {
8709 int contextCount = contextEnd - contextStart;
8710 return p.getTextRunCursor(mChars, contextStart + mStart,
8711 contextCount, flags, offset + mStart, cursorOpt);
8712 }
8713 }
8714
Gilles Debunne60e21862012-01-30 15:04:14 -08008715 private static final class Marquee extends Handler {
8716 // TODO: Add an option to configure this
8717 private static final float MARQUEE_DELTA_MAX = 0.07f;
8718 private static final int MARQUEE_DELAY = 1200;
8719 private static final int MARQUEE_RESTART_DELAY = 1200;
8720 private static final int MARQUEE_RESOLUTION = 1000 / 30;
8721 private static final int MARQUEE_PIXELS_PER_SECOND = 30;
8722
8723 private static final byte MARQUEE_STOPPED = 0x0;
8724 private static final byte MARQUEE_STARTING = 0x1;
8725 private static final byte MARQUEE_RUNNING = 0x2;
8726
8727 private static final int MESSAGE_START = 0x1;
8728 private static final int MESSAGE_TICK = 0x2;
8729 private static final int MESSAGE_RESTART = 0x3;
8730
8731 private final WeakReference<TextView> mView;
8732
8733 private byte mStatus = MARQUEE_STOPPED;
8734 private final float mScrollUnit;
8735 private float mMaxScroll;
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07008736 private float mMaxFadeScroll;
Gilles Debunne60e21862012-01-30 15:04:14 -08008737 private float mGhostStart;
8738 private float mGhostOffset;
8739 private float mFadeStop;
8740 private int mRepeatLimit;
8741
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07008742 private float mScroll;
Gilles Debunne60e21862012-01-30 15:04:14 -08008743
8744 Marquee(TextView v) {
8745 final float density = v.getContext().getResources().getDisplayMetrics().density;
8746 mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / MARQUEE_RESOLUTION;
8747 mView = new WeakReference<TextView>(v);
8748 }
8749
8750 @Override
8751 public void handleMessage(Message msg) {
8752 switch (msg.what) {
8753 case MESSAGE_START:
8754 mStatus = MARQUEE_RUNNING;
8755 tick();
8756 break;
8757 case MESSAGE_TICK:
8758 tick();
8759 break;
8760 case MESSAGE_RESTART:
8761 if (mStatus == MARQUEE_RUNNING) {
8762 if (mRepeatLimit >= 0) {
8763 mRepeatLimit--;
8764 }
8765 start(mRepeatLimit);
8766 }
8767 break;
8768 }
8769 }
8770
8771 void tick() {
8772 if (mStatus != MARQUEE_RUNNING) {
8773 return;
8774 }
8775
8776 removeMessages(MESSAGE_TICK);
8777
8778 final TextView textView = mView.get();
8779 if (textView != null && (textView.isFocused() || textView.isSelected())) {
8780 mScroll += mScrollUnit;
8781 if (mScroll > mMaxScroll) {
8782 mScroll = mMaxScroll;
8783 sendEmptyMessageDelayed(MESSAGE_RESTART, MARQUEE_RESTART_DELAY);
8784 } else {
8785 sendEmptyMessageDelayed(MESSAGE_TICK, MARQUEE_RESOLUTION);
8786 }
8787 textView.invalidate();
8788 }
8789 }
8790
8791 void stop() {
8792 mStatus = MARQUEE_STOPPED;
8793 removeMessages(MESSAGE_START);
8794 removeMessages(MESSAGE_RESTART);
8795 removeMessages(MESSAGE_TICK);
8796 resetScroll();
8797 }
8798
8799 private void resetScroll() {
8800 mScroll = 0.0f;
8801 final TextView textView = mView.get();
8802 if (textView != null) textView.invalidate();
8803 }
8804
8805 void start(int repeatLimit) {
8806 if (repeatLimit == 0) {
8807 stop();
8808 return;
8809 }
8810 mRepeatLimit = repeatLimit;
8811 final TextView textView = mView.get();
8812 if (textView != null && textView.mLayout != null) {
8813 mStatus = MARQUEE_STARTING;
8814 mScroll = 0.0f;
8815 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
8816 textView.getCompoundPaddingRight();
8817 final float lineWidth = textView.mLayout.getLineWidth(0);
8818 final float gap = textWidth / 3.0f;
8819 mGhostStart = lineWidth - textWidth + gap;
8820 mMaxScroll = mGhostStart + textWidth;
8821 mGhostOffset = lineWidth + gap;
8822 mFadeStop = lineWidth + textWidth / 6.0f;
8823 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
8824
8825 textView.invalidate();
8826 sendEmptyMessageDelayed(MESSAGE_START, MARQUEE_DELAY);
8827 }
8828 }
8829
8830 float getGhostOffset() {
8831 return mGhostOffset;
8832 }
8833
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07008834 float getScroll() {
8835 return mScroll;
8836 }
8837
8838 float getMaxFadeScroll() {
8839 return mMaxFadeScroll;
8840 }
8841
Gilles Debunne60e21862012-01-30 15:04:14 -08008842 boolean shouldDrawLeftFade() {
8843 return mScroll <= mFadeStop;
8844 }
8845
8846 boolean shouldDrawGhost() {
8847 return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
8848 }
8849
8850 boolean isRunning() {
8851 return mStatus == MARQUEE_RUNNING;
8852 }
8853
8854 boolean isStopped() {
8855 return mStatus == MARQUEE_STOPPED;
8856 }
8857 }
8858
Gilles Debunne60e21862012-01-30 15:04:14 -08008859 private class ChangeWatcher implements TextWatcher, SpanWatcher {
8860
8861 private CharSequence mBeforeText;
8862
Gilles Debunne60e21862012-01-30 15:04:14 -08008863 public void beforeTextChanged(CharSequence buffer, int start,
8864 int before, int after) {
8865 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start
8866 + " before=" + before + " after=" + after + ": " + buffer);
8867
8868 if (AccessibilityManager.getInstance(mContext).isEnabled()
Svetoslav Ganov72bba582012-11-05 13:53:43 -08008869 && ((!isPasswordInputType(getInputType()) && !hasPasswordTransformationMethod())
8870 || shouldSpeakPasswordsForAccessibility())) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008871 mBeforeText = buffer.toString();
8872 }
8873
8874 TextView.this.sendBeforeTextChanged(buffer, start, before, after);
8875 }
8876
Gilles Debunned88876a2012-03-16 17:34:04 -07008877 public void onTextChanged(CharSequence buffer, int start, int before, int after) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008878 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start
8879 + " before=" + before + " after=" + after + ": " + buffer);
8880 TextView.this.handleTextChanged(buffer, start, before, after);
8881
Gilles Debunne60e21862012-01-30 15:04:14 -08008882 if (AccessibilityManager.getInstance(mContext).isEnabled() &&
8883 (isFocused() || isSelected() && isShown())) {
8884 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
8885 mBeforeText = null;
8886 }
8887 }
8888
8889 public void afterTextChanged(Editable buffer) {
8890 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer);
8891 TextView.this.sendAfterTextChanged(buffer);
8892
8893 if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
8894 MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
8895 }
8896 }
8897
Gilles Debunned88876a2012-03-16 17:34:04 -07008898 public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008899 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
8900 + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
8901 TextView.this.spanChange(buf, what, s, st, e, en);
8902 }
8903
8904 public void onSpanAdded(Spannable buf, Object what, int s, int e) {
8905 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e
8906 + " what=" + what + ": " + buf);
8907 TextView.this.spanChange(buf, what, -1, s, -1, e);
8908 }
8909
8910 public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
8911 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e
8912 + " what=" + what + ": " + buf);
8913 TextView.this.spanChange(buf, what, s, -1, e, -1);
8914 }
satoka67a3cf2011-09-07 17:14:03 +09008915 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008916}