blob: cad7ae329530cff3b741445490a6b865d9281b01 [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;
Philip Milne7b757812012-09-19 18:13:44 -070029import android.graphics.Insets;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030import android.graphics.Paint;
31import android.graphics.Path;
32import android.graphics.Rect;
33import android.graphics.RectF;
34import android.graphics.Typeface;
35import android.graphics.drawable.Drawable;
Gilles Debunne64e54a62010-09-07 19:07:17 -070036import android.inputmethodservice.ExtractEditText;
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +090037import android.os.AsyncTask;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080038import android.os.Bundle;
39import android.os.Handler;
svetoslavganov75986cf2009-05-14 22:28:01 -070040import android.os.Message;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080041import android.os.Parcel;
42import android.os.Parcelable;
43import android.os.SystemClock;
alanv7d624192012-05-21 14:23:17 -070044import android.provider.Settings;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080045import android.text.BoringLayout;
46import android.text.DynamicLayout;
47import android.text.Editable;
48import android.text.GetChars;
49import android.text.GraphicsOperations;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080050import android.text.InputFilter;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -070051import android.text.InputType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080052import android.text.Layout;
53import android.text.ParcelableSpan;
54import android.text.Selection;
55import android.text.SpanWatcher;
56import android.text.Spannable;
svetoslavganov75986cf2009-05-14 22:28:01 -070057import android.text.SpannableString;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080058import android.text.Spanned;
59import android.text.SpannedString;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080060import android.text.StaticLayout;
Doug Feltcb3791202011-07-07 11:57:48 -070061import android.text.TextDirectionHeuristic;
62import android.text.TextDirectionHeuristics;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080063import android.text.TextPaint;
64import android.text.TextUtils;
Adam Powell282e3772011-08-30 16:51:11 -070065import android.text.TextUtils.TruncateAt;
Gilles Debunne0eea6682011-08-29 13:30:31 -070066import android.text.TextWatcher;
Adam Powell7f8f79a2011-07-07 18:35:54 -070067import android.text.method.AllCapsTransformationMethod;
Gilles Debunne86b9c782010-11-11 10:43:48 -080068import android.text.method.ArrowKeyMovementMethod;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080069import android.text.method.DateKeyListener;
70import android.text.method.DateTimeKeyListener;
71import android.text.method.DialerKeyListener;
72import android.text.method.DigitsKeyListener;
73import android.text.method.KeyListener;
74import android.text.method.LinkMovementMethod;
75import android.text.method.MetaKeyKeyListener;
76import android.text.method.MovementMethod;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080077import android.text.method.PasswordTransformationMethod;
78import android.text.method.SingleLineTransformationMethod;
79import android.text.method.TextKeyListener;
svetoslavganov75986cf2009-05-14 22:28:01 -070080import android.text.method.TimeKeyListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080081import android.text.method.TransformationMethod;
Adam Powell7f8f79a2011-07-07 18:35:54 -070082import android.text.method.TransformationMethod2;
Gilles Debunne214a8622011-04-26 15:44:37 -070083import android.text.method.WordIterator;
Gilles Debunneb35ab7b2011-12-05 15:54:00 -080084import android.text.style.CharacterStyle;
Gilles Debunnef3895ed2010-12-21 12:53:58 -080085import android.text.style.ClickableSpan;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080086import android.text.style.ParagraphStyle;
Gilles Debunne6435a562011-08-04 21:22:30 -070087import android.text.style.SpellCheckSpan;
Gilles Debunne2037b822011-04-22 13:07:33 -070088import android.text.style.SuggestionSpan;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080089import android.text.style.URLSpan;
90import android.text.style.UpdateAppearance;
91import android.text.util.Linkify;
92import android.util.AttributeSet;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080093import android.util.FloatMath;
svetoslavganov75986cf2009-05-14 22:28:01 -070094import android.util.Log;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080095import android.util.TypedValue;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -070096import android.view.AccessibilityIterators.TextSegmentIterator;
Gilles Debunne27113f82010-08-23 12:09:14 -070097import android.view.ActionMode;
Gilles Debunnef170a342010-11-11 11:08:59 -080098import android.view.DragEvent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080099import android.view.Gravity;
Gilles Debunnef788a9f2010-07-22 10:17:23 -0700100import android.view.HapticFeedbackConstants;
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800101import android.view.KeyCharacterMap;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800102import android.view.KeyEvent;
Gilles Debunnef788a9f2010-07-22 10:17:23 -0700103import android.view.Menu;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800104import android.view.MenuItem;
105import android.view.MotionEvent;
106import android.view.View;
Gilles Debunne65f60412010-10-15 16:18:35 -0700107import android.view.ViewConfiguration;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800108import android.view.ViewDebug;
Gilles Debunne27113f82010-08-23 12:09:14 -0700109import android.view.ViewGroup.LayoutParams;
Gilles Debunne3784a7f2011-07-15 13:49:38 -0700110import android.view.ViewRootImpl;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800111import android.view.ViewTreeObserver;
svetoslavganov75986cf2009-05-14 22:28:01 -0700112import android.view.accessibility.AccessibilityEvent;
113import android.view.accessibility.AccessibilityManager;
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700114import android.view.accessibility.AccessibilityNodeInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800115import android.view.animation.AnimationUtils;
116import android.view.inputmethod.BaseInputConnection;
117import android.view.inputmethod.CompletionInfo;
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -0800118import android.view.inputmethod.CorrectionInfo;
svetoslavganov75986cf2009-05-14 22:28:01 -0700119import android.view.inputmethod.EditorInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800120import android.view.inputmethod.ExtractedText;
121import android.view.inputmethod.ExtractedTextRequest;
122import android.view.inputmethod.InputConnection;
123import android.view.inputmethod.InputMethodManager;
satok05f24702011-11-02 19:29:35 +0900124import android.view.textservice.SpellCheckerSubtype;
125import android.view.textservice.TextServicesManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800126import android.widget.RemoteViews.RemoteView;
127
Gilles Debunne22378292011-08-12 10:38:52 -0700128import com.android.internal.util.FastMath;
129import com.android.internal.widget.EditableInputConnection;
130
131import org.xmlpull.v1.XmlPullParserException;
132
Gilles Debunne27113f82010-08-23 12:09:14 -0700133import java.io.IOException;
134import java.lang.ref.WeakReference;
135import java.util.ArrayList;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -0700136import java.util.Locale;
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +0900137import java.util.concurrent.locks.ReentrantLock;
Gilles Debunne27113f82010-08-23 12:09:14 -0700138
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800139/**
140 * Displays text to the user and optionally allows them to edit it. A TextView
141 * is a complete text editor, however the basic class is configured to not
142 * allow editing; see {@link EditText} for a subclass that configures the text
143 * view for editing.
144 *
145 * <p>
146 * <b>XML attributes</b>
147 * <p>
148 * See {@link android.R.styleable#TextView TextView Attributes},
149 * {@link android.R.styleable#View View Attributes}
150 *
151 * @attr ref android.R.styleable#TextView_text
152 * @attr ref android.R.styleable#TextView_bufferType
153 * @attr ref android.R.styleable#TextView_hint
154 * @attr ref android.R.styleable#TextView_textColor
155 * @attr ref android.R.styleable#TextView_textColorHighlight
156 * @attr ref android.R.styleable#TextView_textColorHint
Romain Guyd6a463a2009-05-21 23:10:10 -0700157 * @attr ref android.R.styleable#TextView_textAppearance
158 * @attr ref android.R.styleable#TextView_textColorLink
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800159 * @attr ref android.R.styleable#TextView_textSize
160 * @attr ref android.R.styleable#TextView_textScaleX
Raph Leviend570e892012-05-09 11:45:34 -0700161 * @attr ref android.R.styleable#TextView_fontFamily
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800162 * @attr ref android.R.styleable#TextView_typeface
163 * @attr ref android.R.styleable#TextView_textStyle
164 * @attr ref android.R.styleable#TextView_cursorVisible
165 * @attr ref android.R.styleable#TextView_maxLines
166 * @attr ref android.R.styleable#TextView_maxHeight
167 * @attr ref android.R.styleable#TextView_lines
168 * @attr ref android.R.styleable#TextView_height
169 * @attr ref android.R.styleable#TextView_minLines
170 * @attr ref android.R.styleable#TextView_minHeight
171 * @attr ref android.R.styleable#TextView_maxEms
172 * @attr ref android.R.styleable#TextView_maxWidth
173 * @attr ref android.R.styleable#TextView_ems
174 * @attr ref android.R.styleable#TextView_width
175 * @attr ref android.R.styleable#TextView_minEms
176 * @attr ref android.R.styleable#TextView_minWidth
177 * @attr ref android.R.styleable#TextView_gravity
178 * @attr ref android.R.styleable#TextView_scrollHorizontally
179 * @attr ref android.R.styleable#TextView_password
180 * @attr ref android.R.styleable#TextView_singleLine
181 * @attr ref android.R.styleable#TextView_selectAllOnFocus
182 * @attr ref android.R.styleable#TextView_includeFontPadding
183 * @attr ref android.R.styleable#TextView_maxLength
184 * @attr ref android.R.styleable#TextView_shadowColor
185 * @attr ref android.R.styleable#TextView_shadowDx
186 * @attr ref android.R.styleable#TextView_shadowDy
187 * @attr ref android.R.styleable#TextView_shadowRadius
188 * @attr ref android.R.styleable#TextView_autoLink
189 * @attr ref android.R.styleable#TextView_linksClickable
190 * @attr ref android.R.styleable#TextView_numeric
191 * @attr ref android.R.styleable#TextView_digits
192 * @attr ref android.R.styleable#TextView_phoneNumber
193 * @attr ref android.R.styleable#TextView_inputMethod
194 * @attr ref android.R.styleable#TextView_capitalize
195 * @attr ref android.R.styleable#TextView_autoText
196 * @attr ref android.R.styleable#TextView_editable
Romain Guyd6a463a2009-05-21 23:10:10 -0700197 * @attr ref android.R.styleable#TextView_freezesText
198 * @attr ref android.R.styleable#TextView_ellipsize
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800199 * @attr ref android.R.styleable#TextView_drawableTop
200 * @attr ref android.R.styleable#TextView_drawableBottom
201 * @attr ref android.R.styleable#TextView_drawableRight
202 * @attr ref android.R.styleable#TextView_drawableLeft
Fabrice Di Megliod1591092012-03-07 15:34:38 -0800203 * @attr ref android.R.styleable#TextView_drawableStart
204 * @attr ref android.R.styleable#TextView_drawableEnd
Romain Guyd6a463a2009-05-21 23:10:10 -0700205 * @attr ref android.R.styleable#TextView_drawablePadding
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800206 * @attr ref android.R.styleable#TextView_lineSpacingExtra
207 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
208 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
Romain Guyd6a463a2009-05-21 23:10:10 -0700209 * @attr ref android.R.styleable#TextView_inputType
210 * @attr ref android.R.styleable#TextView_imeOptions
211 * @attr ref android.R.styleable#TextView_privateImeOptions
212 * @attr ref android.R.styleable#TextView_imeActionLabel
213 * @attr ref android.R.styleable#TextView_imeActionId
214 * @attr ref android.R.styleable#TextView_editorExtras
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800215 */
216@RemoteView
217public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -0700218 static final String LOG_TAG = "TextView";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800219 static final boolean DEBUG_EXTRACT = false;
Gilles Debunneb7012e842011-02-24 15:40:38 -0800220
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800221 // Enum for the "typeface" XML parameter.
222 // TODO: How can we get this from the XML instead of hardcoding it here?
223 private static final int SANS = 1;
224 private static final int SERIF = 2;
225 private static final int MONOSPACE = 3;
226
227 // Bitfield for the "numeric" XML parameter.
228 // TODO: How can we get this from the XML instead of hardcoding it here?
229 private static final int SIGNED = 2;
230 private static final int DECIMAL = 4;
231
Adam Powell282e3772011-08-30 16:51:11 -0700232 /**
233 * Draw marquee text with fading edges as usual
234 */
235 private static final int MARQUEE_FADE_NORMAL = 0;
236
237 /**
238 * Draw marquee text as ellipsize end while inactive instead of with the fade.
239 * (Useful for devices where the fade can be expensive if overdone)
240 */
241 private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1;
242
243 /**
244 * Draw marquee text with fading edges because it is currently active/animating.
245 */
246 private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2;
247
Gilles Debunne60e21862012-01-30 15:04:14 -0800248 private static final int LINES = 1;
249 private static final int EMS = LINES;
250 private static final int PIXELS = 2;
251
252 private static final RectF TEMP_RECTF = new RectF();
Gilles Debunne60e21862012-01-30 15:04:14 -0800253
254 // XXX should be much larger
255 private static final int VERY_WIDE = 1024*1024;
Gilles Debunne60e21862012-01-30 15:04:14 -0800256 private static final int ANIMATED_SCROLL_GAP = 250;
257
258 private static final InputFilter[] NO_FILTERS = new InputFilter[0];
259 private static final Spanned EMPTY_SPANNED = new SpannedString("");
260
Gilles Debunne60e21862012-01-30 15:04:14 -0800261 private static final int CHANGE_WATCHER_PRIORITY = 100;
262
263 // New state used to change background based on whether this TextView is multiline.
264 private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
265
266 // System wide time for last cut or copy action.
Gilles Debunned88876a2012-03-16 17:34:04 -0700267 static long LAST_CUT_OR_COPY_TIME;
Gilles Debunne60e21862012-01-30 15:04:14 -0800268
Gilles Debunne60e21862012-01-30 15:04:14 -0800269 private ColorStateList mTextColor;
270 private ColorStateList mHintTextColor;
271 private ColorStateList mLinkTextColor;
272 private int mCurTextColor;
273 private int mCurHintTextColor;
274 private boolean mFreezesText;
275 private boolean mTemporaryDetach;
276 private boolean mDispatchTemporaryDetach;
277
278 private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
279 private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
280
281 private float mShadowRadius, mShadowDx, mShadowDy;
282
283 private boolean mPreDrawRegistered;
284
285 private TextUtils.TruncateAt mEllipsize;
286
287 static class Drawables {
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800288 final static int DRAWABLE_NONE = -1;
289 final static int DRAWABLE_RIGHT = 0;
290 final static int DRAWABLE_LEFT = 1;
291
Gilles Debunne60e21862012-01-30 15:04:14 -0800292 final Rect mCompoundRect = new Rect();
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800293
Gilles Debunne60e21862012-01-30 15:04:14 -0800294 Drawable mDrawableTop, mDrawableBottom, mDrawableLeft, mDrawableRight,
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800295 mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp;
296
Gilles Debunne60e21862012-01-30 15:04:14 -0800297 int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight,
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800298 mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp;
299
Gilles Debunne60e21862012-01-30 15:04:14 -0800300 int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight,
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800301 mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp;
302
Gilles Debunne60e21862012-01-30 15:04:14 -0800303 int mDrawablePadding;
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800304
305 int mDrawableSaved = DRAWABLE_NONE;
306
307 public void resolveWithLayoutDirection(int layoutDirection) {
308 switch(layoutDirection) {
309 case LAYOUT_DIRECTION_RTL:
310 if (mDrawableStart != null) {
311 mDrawableRight = mDrawableStart;
312
313 mDrawableSizeRight = mDrawableSizeStart;
314 mDrawableHeightRight = mDrawableHeightStart;
315 }
316 if (mDrawableEnd != null) {
317 mDrawableLeft = mDrawableEnd;
318
319 mDrawableSizeLeft = mDrawableSizeEnd;
320 mDrawableHeightLeft = mDrawableHeightEnd;
321 }
322 break;
323
324 case LAYOUT_DIRECTION_LTR:
325 default:
326 if (mDrawableStart != null) {
327 mDrawableLeft = mDrawableStart;
328
329 mDrawableSizeLeft = mDrawableSizeStart;
330 mDrawableHeightLeft = mDrawableHeightStart;
331 }
332 if (mDrawableEnd != null) {
333 mDrawableRight = mDrawableEnd;
334
335 mDrawableSizeRight = mDrawableSizeEnd;
336 mDrawableHeightRight = mDrawableHeightEnd;
337 }
338 break;
339 }
340 applyErrorDrawableIfNeeded(layoutDirection);
341 updateDrawablesLayoutDirection(layoutDirection);
342 }
343
344 private void updateDrawablesLayoutDirection(int layoutDirection) {
345 if (mDrawableLeft != null) {
346 mDrawableLeft.setLayoutDirection(layoutDirection);
347 }
348 if (mDrawableRight != null) {
349 mDrawableRight.setLayoutDirection(layoutDirection);
350 }
351 if (mDrawableTop != null) {
352 mDrawableTop.setLayoutDirection(layoutDirection);
353 }
354 if (mDrawableBottom != null) {
355 mDrawableBottom.setLayoutDirection(layoutDirection);
356 }
357 }
358
359 public void setErrorDrawable(Drawable dr, TextView tv) {
360 if (mDrawableError != dr && mDrawableError != null) {
361 mDrawableError.setCallback(null);
362 }
363 mDrawableError = dr;
364
365 final Rect compoundRect = mCompoundRect;
366 int[] state = tv.getDrawableState();
367
368 if (mDrawableError != null) {
369 mDrawableError.setState(state);
370 mDrawableError.copyBounds(compoundRect);
371 mDrawableError.setCallback(tv);
372 mDrawableSizeError = compoundRect.width();
373 mDrawableHeightError = compoundRect.height();
374 } else {
375 mDrawableSizeError = mDrawableHeightError = 0;
376 }
377 }
378
379 private void applyErrorDrawableIfNeeded(int layoutDirection) {
380 // first restore the initial state if needed
381 switch (mDrawableSaved) {
382 case DRAWABLE_LEFT:
383 mDrawableLeft = mDrawableTemp;
384 mDrawableSizeLeft = mDrawableSizeTemp;
385 mDrawableHeightLeft = mDrawableHeightTemp;
386 break;
387 case DRAWABLE_RIGHT:
388 mDrawableRight = mDrawableTemp;
389 mDrawableSizeRight = mDrawableSizeTemp;
390 mDrawableHeightRight = mDrawableHeightTemp;
391 break;
392 case DRAWABLE_NONE:
393 default:
394 }
395 // then, if needed, assign the Error drawable to the correct location
396 if (mDrawableError != null) {
397 switch(layoutDirection) {
398 case LAYOUT_DIRECTION_RTL:
399 mDrawableSaved = DRAWABLE_LEFT;
400
401 mDrawableTemp = mDrawableLeft;
402 mDrawableSizeTemp = mDrawableSizeLeft;
403 mDrawableHeightTemp = mDrawableHeightLeft;
404
405 mDrawableLeft = mDrawableError;
406 mDrawableSizeLeft = mDrawableSizeError;
407 mDrawableHeightLeft = mDrawableHeightError;
408 break;
409 case LAYOUT_DIRECTION_LTR:
410 default:
411 mDrawableSaved = DRAWABLE_RIGHT;
412
413 mDrawableTemp = mDrawableRight;
414 mDrawableSizeTemp = mDrawableSizeRight;
415 mDrawableHeightTemp = mDrawableHeightRight;
416
417 mDrawableRight = mDrawableError;
418 mDrawableSizeRight = mDrawableSizeError;
419 mDrawableHeightRight = mDrawableHeightError;
420 break;
421 }
422 }
423 }
Gilles Debunne60e21862012-01-30 15:04:14 -0800424 }
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800425
Gilles Debunned88876a2012-03-16 17:34:04 -0700426 Drawables mDrawables;
Gilles Debunne60e21862012-01-30 15:04:14 -0800427
428 private CharWrapper mCharWrapper;
429
430 private Marquee mMarquee;
431 private boolean mRestartMarquee;
432
433 private int mMarqueeRepeatLimit = 3;
434
435 // The alignment to pass to Layout, or null if not resolved.
436 private Layout.Alignment mLayoutAlignment;
Fabrice Di Megliofa1babd2012-09-04 19:11:25 -0700437 private int mResolvedTextAlignment;
Gilles Debunne60e21862012-01-30 15:04:14 -0800438
Fabrice Di Meglio1957d282012-10-25 17:42:39 -0700439 private int mLastLayoutDirection = -1;
Gilles Debunne60e21862012-01-30 15:04:14 -0800440
441 /**
442 * On some devices the fading edges add a performance penalty if used
443 * extensively in the same layout. This mode indicates how the marquee
444 * is currently being shown, if applicable. (mEllipsize will == MARQUEE)
445 */
446 private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
447
448 /**
449 * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores
450 * the layout that should be used when the mode switches.
451 */
452 private Layout mSavedMarqueeModeLayout;
453
454 @ViewDebug.ExportedProperty(category = "text")
455 private CharSequence mText;
456 private CharSequence mTransformed;
457 private BufferType mBufferType = BufferType.NORMAL;
458
459 private CharSequence mHint;
460 private Layout mHintLayout;
461
462 private MovementMethod mMovement;
463
464 private TransformationMethod mTransformation;
465 private boolean mAllowTransformationLengthChange;
466 private ChangeWatcher mChangeWatcher;
467
468 private ArrayList<TextWatcher> mListeners;
469
470 // display attributes
471 private final TextPaint mTextPaint;
472 private boolean mUserSetTextScaleX;
473 private Layout mLayout;
474
475 private int mGravity = Gravity.TOP | Gravity.START;
476 private boolean mHorizontallyScrolling;
477
478 private int mAutoLinkMask;
479 private boolean mLinksClickable = true;
480
481 private float mSpacingMult = 1.0f;
482 private float mSpacingAdd = 0.0f;
483
484 private int mMaximum = Integer.MAX_VALUE;
485 private int mMaxMode = LINES;
486 private int mMinimum = 0;
487 private int mMinMode = LINES;
488
489 private int mOldMaximum = mMaximum;
490 private int mOldMaxMode = mMaxMode;
491
492 private int mMaxWidth = Integer.MAX_VALUE;
493 private int mMaxWidthMode = PIXELS;
494 private int mMinWidth = 0;
495 private int mMinWidthMode = PIXELS;
496
497 private boolean mSingleLine;
498 private int mDesiredHeightAtMeasure = -1;
499 private boolean mIncludePad = true;
Raph Levienf5c1a872012-10-15 17:22:26 -0700500 private int mDeferScroll = -1;
Gilles Debunne60e21862012-01-30 15:04:14 -0800501
502 // tmp primitives, so we don't alloc them on each draw
503 private Rect mTempRect;
504 private long mLastScroll;
505 private Scroller mScroller;
506
507 private BoringLayout.Metrics mBoring, mHintBoring;
508 private BoringLayout mSavedLayout, mSavedHintLayout;
509
510 private TextDirectionHeuristic mTextDir;
511
512 private InputFilter[] mFilters = NO_FILTERS;
513
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +0900514 private volatile Locale mCurrentSpellCheckerLocaleCache;
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +0900515 private final ReentrantLock mCurrentTextServicesLocaleLock = new ReentrantLock();
516
Gilles Debunne83051b82012-02-24 20:01:13 -0800517 // It is possible to have a selection even when mEditor is null (programmatically set, like when
518 // a link is pressed). These highlight-related fields do not go in mEditor.
Gilles Debunned88876a2012-03-16 17:34:04 -0700519 int mHighlightColor = 0x6633B5E5;
Gilles Debunne83051b82012-02-24 20:01:13 -0800520 private Path mHighlightPath;
521 private final Paint mHighlightPaint;
522 private boolean mHighlightPathBogus = true;
523
Gilles Debunne60e21862012-01-30 15:04:14 -0800524 // Although these fields are specific to editable text, they are not added to Editor because
525 // they are defined by the TextView's style and are theme-dependent.
Gilles Debunned88876a2012-03-16 17:34:04 -0700526 int mCursorDrawableRes;
Gilles Debunne60e21862012-01-30 15:04:14 -0800527 // These four fields, could be moved to Editor, since we know their default values and we
528 // could condition the creation of the Editor to a non standard value. This is however
529 // brittle since the hardcoded values here (such as
530 // com.android.internal.R.drawable.text_select_handle_left) would have to be updated if the
531 // default style is modified.
Gilles Debunned88876a2012-03-16 17:34:04 -0700532 int mTextSelectHandleLeftRes;
533 int mTextSelectHandleRightRes;
534 int mTextSelectHandleRes;
535 int mTextEditSuggestionItemLayout;
Gilles Debunne60e21862012-01-30 15:04:14 -0800536
537 /**
538 * EditText specific data, created on demand when one of the Editor fields is used.
Gilles Debunne5fae9962012-05-08 14:53:20 -0700539 * See {@link #createEditorIfNeeded()}.
Gilles Debunne60e21862012-01-30 15:04:14 -0800540 */
541 private Editor mEditor;
542
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800543 /*
544 * Kick-start the font cache for the zygote process (to pay the cost of
545 * initializing freetype for our default font only once).
546 */
547 static {
548 Paint p = new Paint();
549 p.setAntiAlias(true);
550 // We don't care about the result, just the side-effect of measuring.
551 p.measureText("H");
552 }
553
554 /**
555 * Interface definition for a callback to be invoked when an action is
556 * performed on the editor.
557 */
558 public interface OnEditorActionListener {
559 /**
560 * Called when an action is being performed.
561 *
562 * @param v The view that was clicked.
563 * @param actionId Identifier of the action. This will be either the
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700564 * identifier you supplied, or {@link EditorInfo#IME_NULL
565 * EditorInfo.IME_NULL} if being called due to the enter key
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800566 * being pressed.
567 * @param event If triggered by an enter key, this is the event;
568 * otherwise, this is null.
569 * @return Return true if you have consumed the action, else false.
570 */
571 boolean onEditorAction(TextView v, int actionId, KeyEvent event);
572 }
Gilles Debunne21078e42011-08-02 10:22:35 -0700573
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800574 public TextView(Context context) {
575 this(context, null);
576 }
577
Gilles Debunnec1714022012-01-17 13:59:23 -0800578 public TextView(Context context, AttributeSet attrs) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800579 this(context, attrs, com.android.internal.R.attr.textViewStyle);
580 }
581
Gilles Debunnee15b3582010-06-16 15:17:21 -0700582 @SuppressWarnings("deprecation")
Gilles Debunnec1714022012-01-17 13:59:23 -0800583 public TextView(Context context, AttributeSet attrs, int defStyle) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800584 super(context, attrs, defStyle);
585 mText = "";
586
Christopher Tate1373a8e2011-11-10 19:59:13 -0800587 final Resources res = getResources();
588 final CompatibilityInfo compat = res.getCompatibilityInfo();
589
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800590 mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
Christopher Tate1373a8e2011-11-10 19:59:13 -0800591 mTextPaint.density = res.getDisplayMetrics().density;
592 mTextPaint.setCompatibilityScaling(compat.applicationScale);
Gilles Debunne8cbb4c62011-01-24 12:33:56 -0800593
Gilles Debunne83051b82012-02-24 20:01:13 -0800594 mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
595 mHighlightPaint.setCompatibilityScaling(compat.applicationScale);
596
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800597 mMovement = getDefaultMovementMethod();
Gilles Debunne60e21862012-01-30 15:04:14 -0800598
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800599 mTransformation = null;
600
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800601 int textColorHighlight = 0;
602 ColorStateList textColor = null;
603 ColorStateList textColorHint = null;
604 ColorStateList textColorLink = null;
605 int textSize = 15;
Raph Leviend570e892012-05-09 11:45:34 -0700606 String fontFamily = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800607 int typefaceIndex = -1;
608 int styleIndex = -1;
Adam Powell7f8f79a2011-07-07 18:35:54 -0700609 boolean allCaps = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800610
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700611 final Resources.Theme theme = context.getTheme();
612
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800613 /*
614 * Look the appearance up without checking first if it exists because
615 * almost every TextView has one and it greatly simplifies the logic
616 * to be able to parse the appearance first and then let specific tags
617 * for this View override it.
618 */
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700619 TypedArray a = theme.obtainStyledAttributes(
620 attrs, com.android.internal.R.styleable.TextViewAppearance, defStyle, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800621 TypedArray appearance = null;
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700622 int ap = a.getResourceId(
623 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);
624 a.recycle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800625 if (ap != -1) {
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700626 appearance = theme.obtainStyledAttributes(
627 ap, com.android.internal.R.styleable.TextAppearance);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800628 }
629 if (appearance != null) {
630 int n = appearance.getIndexCount();
631 for (int i = 0; i < n; i++) {
632 int attr = appearance.getIndex(i);
633
634 switch (attr) {
635 case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
636 textColorHighlight = appearance.getColor(attr, textColorHighlight);
637 break;
638
639 case com.android.internal.R.styleable.TextAppearance_textColor:
640 textColor = appearance.getColorStateList(attr);
641 break;
642
643 case com.android.internal.R.styleable.TextAppearance_textColorHint:
644 textColorHint = appearance.getColorStateList(attr);
645 break;
646
647 case com.android.internal.R.styleable.TextAppearance_textColorLink:
648 textColorLink = appearance.getColorStateList(attr);
649 break;
650
651 case com.android.internal.R.styleable.TextAppearance_textSize:
652 textSize = appearance.getDimensionPixelSize(attr, textSize);
653 break;
654
655 case com.android.internal.R.styleable.TextAppearance_typeface:
656 typefaceIndex = appearance.getInt(attr, -1);
657 break;
658
Raph Leviend570e892012-05-09 11:45:34 -0700659 case com.android.internal.R.styleable.TextAppearance_fontFamily:
660 fontFamily = appearance.getString(attr);
661 break;
662
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800663 case com.android.internal.R.styleable.TextAppearance_textStyle:
664 styleIndex = appearance.getInt(attr, -1);
665 break;
Adam Powell7f8f79a2011-07-07 18:35:54 -0700666
667 case com.android.internal.R.styleable.TextAppearance_textAllCaps:
668 allCaps = appearance.getBoolean(attr, false);
669 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800670 }
671 }
672
673 appearance.recycle();
674 }
675
676 boolean editable = getDefaultEditable();
677 CharSequence inputMethod = null;
678 int numeric = 0;
679 CharSequence digits = null;
680 boolean phone = false;
681 boolean autotext = false;
682 int autocap = -1;
683 int buffertype = 0;
684 boolean selectallonfocus = false;
685 Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -0700686 drawableBottom = null, drawableStart = null, drawableEnd = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800687 int drawablePadding = 0;
688 int ellipsize = -1;
Gilles Debunnef95449d2010-11-05 13:54:13 -0700689 boolean singleLine = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800690 int maxlength = -1;
691 CharSequence text = "";
Romain Guy4dc4f732009-06-19 15:16:40 -0700692 CharSequence hint = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800693 int shadowcolor = 0;
694 float dx = 0, dy = 0, r = 0;
695 boolean password = false;
696 int inputType = EditorInfo.TYPE_NULL;
697
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700698 a = theme.obtainStyledAttributes(
699 attrs, com.android.internal.R.styleable.TextView, defStyle, 0);
700
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800701 int n = a.getIndexCount();
702 for (int i = 0; i < n; i++) {
703 int attr = a.getIndex(i);
704
705 switch (attr) {
706 case com.android.internal.R.styleable.TextView_editable:
707 editable = a.getBoolean(attr, editable);
708 break;
709
710 case com.android.internal.R.styleable.TextView_inputMethod:
711 inputMethod = a.getText(attr);
712 break;
713
714 case com.android.internal.R.styleable.TextView_numeric:
715 numeric = a.getInt(attr, numeric);
716 break;
717
718 case com.android.internal.R.styleable.TextView_digits:
719 digits = a.getText(attr);
720 break;
721
722 case com.android.internal.R.styleable.TextView_phoneNumber:
723 phone = a.getBoolean(attr, phone);
724 break;
725
726 case com.android.internal.R.styleable.TextView_autoText:
727 autotext = a.getBoolean(attr, autotext);
728 break;
729
730 case com.android.internal.R.styleable.TextView_capitalize:
731 autocap = a.getInt(attr, autocap);
732 break;
733
734 case com.android.internal.R.styleable.TextView_bufferType:
735 buffertype = a.getInt(attr, buffertype);
736 break;
737
738 case com.android.internal.R.styleable.TextView_selectAllOnFocus:
739 selectallonfocus = a.getBoolean(attr, selectallonfocus);
740 break;
741
742 case com.android.internal.R.styleable.TextView_autoLink:
743 mAutoLinkMask = a.getInt(attr, 0);
744 break;
745
746 case com.android.internal.R.styleable.TextView_linksClickable:
747 mLinksClickable = a.getBoolean(attr, true);
748 break;
749
750 case com.android.internal.R.styleable.TextView_drawableLeft:
751 drawableLeft = a.getDrawable(attr);
752 break;
753
754 case com.android.internal.R.styleable.TextView_drawableTop:
755 drawableTop = a.getDrawable(attr);
756 break;
757
758 case com.android.internal.R.styleable.TextView_drawableRight:
759 drawableRight = a.getDrawable(attr);
760 break;
761
762 case com.android.internal.R.styleable.TextView_drawableBottom:
763 drawableBottom = a.getDrawable(attr);
764 break;
765
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -0700766 case com.android.internal.R.styleable.TextView_drawableStart:
767 drawableStart = a.getDrawable(attr);
768 break;
769
770 case com.android.internal.R.styleable.TextView_drawableEnd:
771 drawableEnd = a.getDrawable(attr);
772 break;
773
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800774 case com.android.internal.R.styleable.TextView_drawablePadding:
775 drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
776 break;
777
778 case com.android.internal.R.styleable.TextView_maxLines:
779 setMaxLines(a.getInt(attr, -1));
780 break;
781
782 case com.android.internal.R.styleable.TextView_maxHeight:
783 setMaxHeight(a.getDimensionPixelSize(attr, -1));
784 break;
785
786 case com.android.internal.R.styleable.TextView_lines:
787 setLines(a.getInt(attr, -1));
788 break;
789
790 case com.android.internal.R.styleable.TextView_height:
791 setHeight(a.getDimensionPixelSize(attr, -1));
792 break;
793
794 case com.android.internal.R.styleable.TextView_minLines:
795 setMinLines(a.getInt(attr, -1));
796 break;
797
798 case com.android.internal.R.styleable.TextView_minHeight:
799 setMinHeight(a.getDimensionPixelSize(attr, -1));
800 break;
801
802 case com.android.internal.R.styleable.TextView_maxEms:
803 setMaxEms(a.getInt(attr, -1));
804 break;
805
806 case com.android.internal.R.styleable.TextView_maxWidth:
807 setMaxWidth(a.getDimensionPixelSize(attr, -1));
808 break;
809
810 case com.android.internal.R.styleable.TextView_ems:
811 setEms(a.getInt(attr, -1));
812 break;
813
814 case com.android.internal.R.styleable.TextView_width:
815 setWidth(a.getDimensionPixelSize(attr, -1));
816 break;
817
818 case com.android.internal.R.styleable.TextView_minEms:
819 setMinEms(a.getInt(attr, -1));
820 break;
821
822 case com.android.internal.R.styleable.TextView_minWidth:
823 setMinWidth(a.getDimensionPixelSize(attr, -1));
824 break;
825
826 case com.android.internal.R.styleable.TextView_gravity:
827 setGravity(a.getInt(attr, -1));
828 break;
829
830 case com.android.internal.R.styleable.TextView_hint:
Romain Guy4dc4f732009-06-19 15:16:40 -0700831 hint = a.getText(attr);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800832 break;
833
834 case com.android.internal.R.styleable.TextView_text:
835 text = a.getText(attr);
836 break;
837
838 case com.android.internal.R.styleable.TextView_scrollHorizontally:
839 if (a.getBoolean(attr, false)) {
840 setHorizontallyScrolling(true);
841 }
842 break;
843
844 case com.android.internal.R.styleable.TextView_singleLine:
845 singleLine = a.getBoolean(attr, singleLine);
846 break;
847
848 case com.android.internal.R.styleable.TextView_ellipsize:
849 ellipsize = a.getInt(attr, ellipsize);
850 break;
851
852 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
853 setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
854 break;
855
856 case com.android.internal.R.styleable.TextView_includeFontPadding:
857 if (!a.getBoolean(attr, true)) {
858 setIncludeFontPadding(false);
859 }
860 break;
861
862 case com.android.internal.R.styleable.TextView_cursorVisible:
863 if (!a.getBoolean(attr, true)) {
864 setCursorVisible(false);
865 }
866 break;
867
868 case com.android.internal.R.styleable.TextView_maxLength:
869 maxlength = a.getInt(attr, -1);
870 break;
871
872 case com.android.internal.R.styleable.TextView_textScaleX:
873 setTextScaleX(a.getFloat(attr, 1.0f));
874 break;
875
876 case com.android.internal.R.styleable.TextView_freezesText:
877 mFreezesText = a.getBoolean(attr, false);
878 break;
879
880 case com.android.internal.R.styleable.TextView_shadowColor:
881 shadowcolor = a.getInt(attr, 0);
882 break;
883
884 case com.android.internal.R.styleable.TextView_shadowDx:
885 dx = a.getFloat(attr, 0);
886 break;
887
888 case com.android.internal.R.styleable.TextView_shadowDy:
889 dy = a.getFloat(attr, 0);
890 break;
891
892 case com.android.internal.R.styleable.TextView_shadowRadius:
893 r = a.getFloat(attr, 0);
894 break;
895
896 case com.android.internal.R.styleable.TextView_enabled:
897 setEnabled(a.getBoolean(attr, isEnabled()));
898 break;
899
900 case com.android.internal.R.styleable.TextView_textColorHighlight:
901 textColorHighlight = a.getColor(attr, textColorHighlight);
902 break;
903
904 case com.android.internal.R.styleable.TextView_textColor:
905 textColor = a.getColorStateList(attr);
906 break;
907
908 case com.android.internal.R.styleable.TextView_textColorHint:
909 textColorHint = a.getColorStateList(attr);
910 break;
911
912 case com.android.internal.R.styleable.TextView_textColorLink:
913 textColorLink = a.getColorStateList(attr);
914 break;
915
916 case com.android.internal.R.styleable.TextView_textSize:
917 textSize = a.getDimensionPixelSize(attr, textSize);
918 break;
919
920 case com.android.internal.R.styleable.TextView_typeface:
921 typefaceIndex = a.getInt(attr, typefaceIndex);
922 break;
923
924 case com.android.internal.R.styleable.TextView_textStyle:
925 styleIndex = a.getInt(attr, styleIndex);
926 break;
927
Raph Leviend570e892012-05-09 11:45:34 -0700928 case com.android.internal.R.styleable.TextView_fontFamily:
929 fontFamily = a.getString(attr);
930 break;
931
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800932 case com.android.internal.R.styleable.TextView_password:
933 password = a.getBoolean(attr, password);
934 break;
935
936 case com.android.internal.R.styleable.TextView_lineSpacingExtra:
937 mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
938 break;
939
940 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
941 mSpacingMult = a.getFloat(attr, mSpacingMult);
942 break;
943
944 case com.android.internal.R.styleable.TextView_inputType:
Gilles Debunne60e21862012-01-30 15:04:14 -0800945 inputType = a.getInt(attr, EditorInfo.TYPE_NULL);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800946 break;
947
948 case com.android.internal.R.styleable.TextView_imeOptions:
Gilles Debunne5fae9962012-05-08 14:53:20 -0700949 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -0700950 mEditor.createInputContentTypeIfNeeded();
951 mEditor.mInputContentType.imeOptions = a.getInt(attr,
952 mEditor.mInputContentType.imeOptions);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800953 break;
954
955 case com.android.internal.R.styleable.TextView_imeActionLabel:
Gilles Debunne5fae9962012-05-08 14:53:20 -0700956 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -0700957 mEditor.createInputContentTypeIfNeeded();
958 mEditor.mInputContentType.imeActionLabel = a.getText(attr);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800959 break;
960
961 case com.android.internal.R.styleable.TextView_imeActionId:
Gilles Debunne5fae9962012-05-08 14:53:20 -0700962 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -0700963 mEditor.createInputContentTypeIfNeeded();
964 mEditor.mInputContentType.imeActionId = a.getInt(attr,
965 mEditor.mInputContentType.imeActionId);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800966 break;
967
968 case com.android.internal.R.styleable.TextView_privateImeOptions:
969 setPrivateImeOptions(a.getString(attr));
970 break;
971
972 case com.android.internal.R.styleable.TextView_editorExtras:
973 try {
974 setInputExtras(a.getResourceId(attr, 0));
975 } catch (XmlPullParserException e) {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -0700976 Log.w(LOG_TAG, "Failure reading input extras", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800977 } catch (IOException e) {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -0700978 Log.w(LOG_TAG, "Failure reading input extras", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800979 }
980 break;
Adam Powellb08013c2010-09-16 16:28:11 -0700981
Gilles Debunnef75c97e2011-02-10 16:09:53 -0800982 case com.android.internal.R.styleable.TextView_textCursorDrawable:
983 mCursorDrawableRes = a.getResourceId(attr, 0);
984 break;
985
Adam Powellb08013c2010-09-16 16:28:11 -0700986 case com.android.internal.R.styleable.TextView_textSelectHandleLeft:
987 mTextSelectHandleLeftRes = a.getResourceId(attr, 0);
988 break;
989
990 case com.android.internal.R.styleable.TextView_textSelectHandleRight:
991 mTextSelectHandleRightRes = a.getResourceId(attr, 0);
992 break;
993
994 case com.android.internal.R.styleable.TextView_textSelectHandle:
995 mTextSelectHandleRes = a.getResourceId(attr, 0);
996 break;
Gilles Debunne7b9652b2010-10-26 16:27:12 -0700997
Gilles Debunne69340442011-03-31 13:37:51 -0700998 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout:
999 mTextEditSuggestionItemLayout = a.getResourceId(attr, 0);
1000 break;
1001
Gilles Debunne86b9c782010-11-11 10:43:48 -08001002 case com.android.internal.R.styleable.TextView_textIsSelectable:
Gilles Debunne60e21862012-01-30 15:04:14 -08001003 setTextIsSelectable(a.getBoolean(attr, false));
Gilles Debunne86b9c782010-11-11 10:43:48 -08001004 break;
Gilles Debunnef3a135b2011-05-23 16:28:47 -07001005
Adam Powell7f8f79a2011-07-07 18:35:54 -07001006 case com.android.internal.R.styleable.TextView_textAllCaps:
1007 allCaps = a.getBoolean(attr, false);
1008 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001009 }
1010 }
1011 a.recycle();
1012
1013 BufferType bufferType = BufferType.EDITABLE;
1014
Gilles Debunned7483bf2010-11-10 10:47:45 -08001015 final int variation =
1016 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
1017 final boolean passwordInputType = variation
1018 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
1019 final boolean webPasswordInputType = variation
1020 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD);
Ken Wakasa82d731a2010-12-24 23:42:41 +09001021 final boolean numberPasswordInputType = variation
1022 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
Gilles Debunned7483bf2010-11-10 10:47:45 -08001023
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001024 if (inputMethod != null) {
Gilles Debunnee15b3582010-06-16 15:17:21 -07001025 Class<?> c;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001026
1027 try {
1028 c = Class.forName(inputMethod.toString());
1029 } catch (ClassNotFoundException ex) {
1030 throw new RuntimeException(ex);
1031 }
1032
1033 try {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001034 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001035 mEditor.mKeyListener = (KeyListener) c.newInstance();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001036 } catch (InstantiationException ex) {
1037 throw new RuntimeException(ex);
1038 } catch (IllegalAccessException ex) {
1039 throw new RuntimeException(ex);
1040 }
1041 try {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001042 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001043 ? inputType
Gilles Debunne2d373a12012-04-20 15:32:19 -07001044 : mEditor.mKeyListener.getInputType();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001045 } catch (IncompatibleClassChangeError e) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001046 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001047 }
1048 } else if (digits != null) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001049 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001050 mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString());
Dianne Hackborn7ed6ee52009-09-10 18:41:28 -07001051 // If no input type was specified, we will default to generic
1052 // text, since we can't tell the IME about the set of digits
1053 // that was selected.
Gilles Debunne2d373a12012-04-20 15:32:19 -07001054 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
Dianne Hackborn7ed6ee52009-09-10 18:41:28 -07001055 ? inputType : EditorInfo.TYPE_CLASS_TEXT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001056 } else if (inputType != EditorInfo.TYPE_NULL) {
1057 setInputType(inputType, true);
Gilles Debunne91a08cf2010-11-08 17:34:49 -08001058 // If set, the input type overrides what was set using the deprecated singleLine flag.
1059 singleLine = !isMultilineInputType(inputType);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001060 } else if (phone) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001061 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001062 mEditor.mKeyListener = DialerKeyListener.getInstance();
1063 mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001064 } else if (numeric != 0) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001065 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001066 mEditor.mKeyListener = DigitsKeyListener.getInstance((numeric & SIGNED) != 0,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001067 (numeric & DECIMAL) != 0);
1068 inputType = EditorInfo.TYPE_CLASS_NUMBER;
1069 if ((numeric & SIGNED) != 0) {
1070 inputType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED;
1071 }
1072 if ((numeric & DECIMAL) != 0) {
1073 inputType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL;
1074 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07001075 mEditor.mInputType = inputType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001076 } else if (autotext || autocap != -1) {
1077 TextKeyListener.Capitalize cap;
1078
1079 inputType = EditorInfo.TYPE_CLASS_TEXT;
Gilles Debunnef95449d2010-11-05 13:54:13 -07001080
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001081 switch (autocap) {
1082 case 1:
1083 cap = TextKeyListener.Capitalize.SENTENCES;
1084 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
1085 break;
1086
1087 case 2:
1088 cap = TextKeyListener.Capitalize.WORDS;
1089 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
1090 break;
1091
1092 case 3:
1093 cap = TextKeyListener.Capitalize.CHARACTERS;
1094 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
1095 break;
1096
1097 default:
1098 cap = TextKeyListener.Capitalize.NONE;
1099 break;
1100 }
1101
Gilles Debunne5fae9962012-05-08 14:53:20 -07001102 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001103 mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap);
1104 mEditor.mInputType = inputType;
Gilles Debunne60e21862012-01-30 15:04:14 -08001105 } else if (isTextSelectable()) {
Gilles Debunne86b9c782010-11-11 10:43:48 -08001106 // Prevent text changes from keyboard.
Gilles Debunne60e21862012-01-30 15:04:14 -08001107 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001108 mEditor.mKeyListener = null;
1109 mEditor.mInputType = EditorInfo.TYPE_NULL;
Gilles Debunne60e21862012-01-30 15:04:14 -08001110 }
Gilles Debunne86b9c782010-11-11 10:43:48 -08001111 bufferType = BufferType.SPANNABLE;
Gilles Debunne86b9c782010-11-11 10:43:48 -08001112 // So that selection can be changed using arrow keys and touch is handled.
1113 setMovementMethod(ArrowKeyMovementMethod.getInstance());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001114 } else if (editable) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001115 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001116 mEditor.mKeyListener = TextKeyListener.getInstance();
1117 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001118 } else {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001119 if (mEditor != null) mEditor.mKeyListener = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001120
1121 switch (buffertype) {
1122 case 0:
1123 bufferType = BufferType.NORMAL;
1124 break;
1125 case 1:
1126 bufferType = BufferType.SPANNABLE;
1127 break;
1128 case 2:
1129 bufferType = BufferType.EDITABLE;
1130 break;
1131 }
1132 }
1133
Gilles Debunne2d373a12012-04-20 15:32:19 -07001134 if (mEditor != null) mEditor.adjustInputType(password, passwordInputType,
1135 webPasswordInputType, numberPasswordInputType);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001136
1137 if (selectallonfocus) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001138 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001139 mEditor.mSelectAllOnFocus = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001140
1141 if (bufferType == BufferType.NORMAL)
1142 bufferType = BufferType.SPANNABLE;
1143 }
1144
1145 setCompoundDrawablesWithIntrinsicBounds(
1146 drawableLeft, drawableTop, drawableRight, drawableBottom);
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001147 setRelativeDrawablesIfNeeded(drawableStart, drawableEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001148 setCompoundDrawablePadding(drawablePadding);
1149
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08001150 // Same as setSingleLine(), but make sure the transformation method and the maximum number
Gilles Debunne066460f2010-12-15 17:31:51 -08001151 // of lines of height are unchanged for multi-line TextViews.
Gilles Debunned7483bf2010-11-10 10:47:45 -08001152 setInputTypeSingleLine(singleLine);
Gilles Debunne066460f2010-12-15 17:31:51 -08001153 applySingleLine(singleLine, singleLine, singleLine);
Gilles Debunned7483bf2010-11-10 10:47:45 -08001154
Gilles Debunne60e21862012-01-30 15:04:14 -08001155 if (singleLine && getKeyListener() == null && ellipsize < 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001156 ellipsize = 3; // END
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001157 }
1158
1159 switch (ellipsize) {
1160 case 1:
1161 setEllipsize(TextUtils.TruncateAt.START);
1162 break;
1163 case 2:
1164 setEllipsize(TextUtils.TruncateAt.MIDDLE);
1165 break;
1166 case 3:
1167 setEllipsize(TextUtils.TruncateAt.END);
1168 break;
1169 case 4:
Adam Powell282e3772011-08-30 16:51:11 -07001170 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) {
1171 setHorizontalFadingEdgeEnabled(true);
1172 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
1173 } else {
1174 setHorizontalFadingEdgeEnabled(false);
1175 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
1176 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001177 setEllipsize(TextUtils.TruncateAt.MARQUEE);
1178 break;
1179 }
1180
1181 setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000));
1182 setHintTextColor(textColorHint);
1183 setLinkTextColor(textColorLink);
1184 if (textColorHighlight != 0) {
1185 setHighlightColor(textColorHighlight);
1186 }
1187 setRawTextSize(textSize);
1188
Adam Powell7f8f79a2011-07-07 18:35:54 -07001189 if (allCaps) {
1190 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
1191 }
1192
Ken Wakasa82d731a2010-12-24 23:42:41 +09001193 if (password || passwordInputType || webPasswordInputType || numberPasswordInputType) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001194 setTransformationMethod(PasswordTransformationMethod.getInstance());
1195 typefaceIndex = MONOSPACE;
Gilles Debunne2d373a12012-04-20 15:32:19 -07001196 } else if (mEditor != null &&
1197 (mEditor.mInputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
Gilles Debunned7483bf2010-11-10 10:47:45 -08001198 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08001199 typefaceIndex = MONOSPACE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001200 }
1201
Raph Leviend570e892012-05-09 11:45:34 -07001202 setTypefaceFromAttrs(fontFamily, typefaceIndex, styleIndex);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001203
1204 if (shadowcolor != 0) {
1205 setShadowLayer(r, dx, dy, shadowcolor);
1206 }
1207
1208 if (maxlength >= 0) {
1209 setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
1210 } else {
1211 setFilters(NO_FILTERS);
1212 }
1213
1214 setText(text, bufferType);
Romain Guy4dc4f732009-06-19 15:16:40 -07001215 if (hint != null) setHint(hint);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001216
1217 /*
1218 * Views are not normally focusable unless specified to be.
1219 * However, TextViews that have input or movement methods *are*
1220 * focusable by default.
1221 */
1222 a = context.obtainStyledAttributes(attrs,
1223 com.android.internal.R.styleable.View,
1224 defStyle, 0);
1225
Gilles Debunne60e21862012-01-30 15:04:14 -08001226 boolean focusable = mMovement != null || getKeyListener() != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001227 boolean clickable = focusable;
1228 boolean longClickable = focusable;
1229
1230 n = a.getIndexCount();
1231 for (int i = 0; i < n; i++) {
1232 int attr = a.getIndex(i);
1233
1234 switch (attr) {
1235 case com.android.internal.R.styleable.View_focusable:
1236 focusable = a.getBoolean(attr, focusable);
1237 break;
1238
1239 case com.android.internal.R.styleable.View_clickable:
1240 clickable = a.getBoolean(attr, clickable);
1241 break;
1242
1243 case com.android.internal.R.styleable.View_longClickable:
1244 longClickable = a.getBoolean(attr, longClickable);
1245 break;
1246 }
1247 }
1248 a.recycle();
1249
1250 setFocusable(focusable);
1251 setClickable(clickable);
1252 setLongClickable(longClickable);
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07001253
Gilles Debunned88876a2012-03-16 17:34:04 -07001254 if (mEditor != null) mEditor.prepareCursorControllers();
Svetoslav Ganov42138042012-03-20 11:51:39 -07001255
1256 // If not explicitly specified this view is important for accessibility.
1257 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
1258 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
1259 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001260 }
1261
Raph Leviend570e892012-05-09 11:45:34 -07001262 private void setTypefaceFromAttrs(String familyName, int typefaceIndex, int styleIndex) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001263 Typeface tf = null;
Raph Leviend570e892012-05-09 11:45:34 -07001264 if (familyName != null) {
1265 tf = Typeface.create(familyName, styleIndex);
1266 if (tf != null) {
1267 setTypeface(tf);
1268 return;
1269 }
1270 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001271 switch (typefaceIndex) {
1272 case SANS:
1273 tf = Typeface.SANS_SERIF;
1274 break;
1275
1276 case SERIF:
1277 tf = Typeface.SERIF;
1278 break;
1279
1280 case MONOSPACE:
1281 tf = Typeface.MONOSPACE;
1282 break;
1283 }
1284
1285 setTypeface(tf, styleIndex);
1286 }
1287
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001288 private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) {
1289 boolean hasRelativeDrawables = (start != null) || (end != null);
1290 if (hasRelativeDrawables) {
1291 Drawables dr = mDrawables;
1292 if (dr == null) {
1293 mDrawables = dr = new Drawables();
1294 }
1295 final Rect compoundRect = dr.mCompoundRect;
1296 int[] state = getDrawableState();
1297 if (start != null) {
1298 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
1299 start.setState(state);
1300 start.copyBounds(compoundRect);
1301 start.setCallback(this);
1302
1303 dr.mDrawableStart = start;
1304 dr.mDrawableSizeStart = compoundRect.width();
1305 dr.mDrawableHeightStart = compoundRect.height();
1306 } else {
1307 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
1308 }
1309 if (end != null) {
1310 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
1311 end.setState(state);
1312 end.copyBounds(compoundRect);
1313 end.setCallback(this);
1314
1315 dr.mDrawableEnd = end;
1316 dr.mDrawableSizeEnd = compoundRect.width();
1317 dr.mDrawableHeightEnd = compoundRect.height();
1318 } else {
1319 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
1320 }
1321 }
1322 }
1323
Janos Levai042856c2010-10-15 02:53:58 +03001324 @Override
1325 public void setEnabled(boolean enabled) {
1326 if (enabled == isEnabled()) {
1327 return;
1328 }
1329
1330 if (!enabled) {
1331 // Hide the soft input if the currently active TextView is disabled
1332 InputMethodManager imm = InputMethodManager.peekInstance();
1333 if (imm != null && imm.isActive(this)) {
1334 imm.hideSoftInputFromWindow(getWindowToken(), 0);
1335 }
1336 }
Gilles Debunne545c4d42011-11-29 10:37:15 -08001337
Janos Levai042856c2010-10-15 02:53:58 +03001338 super.setEnabled(enabled);
Gilles Debunne545c4d42011-11-29 10:37:15 -08001339
Dianne Hackbornbc823852011-09-18 17:19:50 -07001340 if (enabled) {
1341 // Make sure IME is updated with current editor info.
1342 InputMethodManager imm = InputMethodManager.peekInstance();
1343 if (imm != null) imm.restartInput(this);
1344 }
Mark Wagnerf8185112011-10-25 16:33:41 -07001345
Gilles Debunne33b7de852012-03-12 11:57:48 -07001346 // Will change text color
Gilles Debunned88876a2012-03-16 17:34:04 -07001347 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001348 mEditor.invalidateTextDisplayList();
1349 mEditor.prepareCursorControllers();
Gilles Debunne545c4d42011-11-29 10:37:15 -08001350
Gilles Debunned88876a2012-03-16 17:34:04 -07001351 // start or stop the cursor blinking as appropriate
Gilles Debunne2d373a12012-04-20 15:32:19 -07001352 mEditor.makeBlink();
Gilles Debunned88876a2012-03-16 17:34:04 -07001353 }
Janos Levai042856c2010-10-15 02:53:58 +03001354 }
1355
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001356 /**
1357 * Sets the typeface and style in which the text should be displayed,
1358 * and turns on the fake bold and italic bits in the Paint if the
1359 * Typeface that you provided does not have all the bits in the
1360 * style that you specified.
1361 *
1362 * @attr ref android.R.styleable#TextView_typeface
1363 * @attr ref android.R.styleable#TextView_textStyle
1364 */
1365 public void setTypeface(Typeface tf, int style) {
1366 if (style > 0) {
1367 if (tf == null) {
1368 tf = Typeface.defaultFromStyle(style);
1369 } else {
1370 tf = Typeface.create(tf, style);
1371 }
1372
1373 setTypeface(tf);
1374 // now compute what (if any) algorithmic styling is needed
1375 int typefaceStyle = tf != null ? tf.getStyle() : 0;
1376 int need = style & ~typefaceStyle;
1377 mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
1378 mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
1379 } else {
Victoria Leaseaa0980a2012-06-11 14:46:04 -07001380 mTextPaint.setFakeBoldText(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001381 mTextPaint.setTextSkewX(0);
1382 setTypeface(tf);
1383 }
1384 }
1385
1386 /**
1387 * Subclasses override this to specify that they have a KeyListener
1388 * by default even if not specifically called for in the XML options.
1389 */
1390 protected boolean getDefaultEditable() {
1391 return false;
1392 }
1393
1394 /**
1395 * Subclasses override this to specify a default movement method.
1396 */
1397 protected MovementMethod getDefaultMovementMethod() {
1398 return null;
1399 }
1400
1401 /**
1402 * Return the text the TextView is displaying. If setText() was called with
1403 * an argument of BufferType.SPANNABLE or BufferType.EDITABLE, you can cast
1404 * the return value from this method to Spannable or Editable, respectively.
1405 *
1406 * Note: The content of the return value should not be modified. If you want
1407 * a modifiable one, you should make your own copy first.
Gilles Debunnef03acef2012-04-30 19:26:19 -07001408 *
1409 * @attr ref android.R.styleable#TextView_text
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001410 */
1411 @ViewDebug.CapturedViewProperty
1412 public CharSequence getText() {
1413 return mText;
1414 }
1415
1416 /**
1417 * Returns the length, in characters, of the text managed by this TextView
1418 */
1419 public int length() {
1420 return mText.length();
1421 }
1422
1423 /**
1424 * Return the text the TextView is displaying as an Editable object. If
1425 * the text is not editable, null is returned.
1426 *
1427 * @see #getText
1428 */
1429 public Editable getEditableText() {
1430 return (mText instanceof Editable) ? (Editable)mText : null;
1431 }
1432
1433 /**
1434 * @return the height of one standard line in pixels. Note that markup
1435 * within the text can cause individual lines to be taller or shorter
1436 * than this height, and the layout may contain additional first-
1437 * or last-line padding.
1438 */
1439 public int getLineHeight() {
Gilles Debunne96e6b8b2010-12-14 13:43:45 -08001440 return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001441 }
1442
1443 /**
1444 * @return the Layout that is currently being used to display the text.
1445 * This can be null if the text or width has recently changes.
1446 */
1447 public final Layout getLayout() {
1448 return mLayout;
1449 }
1450
1451 /**
Fabrice Di Meglio0ed59fa2012-05-29 20:32:51 -07001452 * @return the Layout that is currently being used to display the hint text.
1453 * This can be null.
1454 */
1455 final Layout getHintLayout() {
1456 return mHintLayout;
1457 }
1458
1459 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001460 * @return the current key listener for this TextView.
1461 * This will frequently be null for non-EditText TextViews.
Gilles Debunnef03acef2012-04-30 19:26:19 -07001462 *
1463 * @attr ref android.R.styleable#TextView_numeric
1464 * @attr ref android.R.styleable#TextView_digits
1465 * @attr ref android.R.styleable#TextView_phoneNumber
1466 * @attr ref android.R.styleable#TextView_inputMethod
1467 * @attr ref android.R.styleable#TextView_capitalize
1468 * @attr ref android.R.styleable#TextView_autoText
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001469 */
1470 public final KeyListener getKeyListener() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001471 return mEditor == null ? null : mEditor.mKeyListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001472 }
1473
1474 /**
1475 * Sets the key listener to be used with this TextView. This can be null
1476 * to disallow user input. Note that this method has significant and
1477 * subtle interactions with soft keyboards and other input method:
1478 * see {@link KeyListener#getInputType() KeyListener.getContentType()}
1479 * for important details. Calling this method will replace the current
1480 * content type of the text view with the content type returned by the
1481 * key listener.
1482 * <p>
1483 * Be warned that if you want a TextView with a key listener or movement
1484 * method not to be focusable, or if you want a TextView without a
1485 * key listener or movement method to be focusable, you must call
1486 * {@link #setFocusable} again after calling this to get the focusability
1487 * back the way you want it.
1488 *
1489 * @attr ref android.R.styleable#TextView_numeric
1490 * @attr ref android.R.styleable#TextView_digits
1491 * @attr ref android.R.styleable#TextView_phoneNumber
1492 * @attr ref android.R.styleable#TextView_inputMethod
1493 * @attr ref android.R.styleable#TextView_capitalize
1494 * @attr ref android.R.styleable#TextView_autoText
1495 */
1496 public void setKeyListener(KeyListener input) {
1497 setKeyListenerOnly(input);
1498 fixFocusableAndClickableSettings();
1499
1500 if (input != null) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001501 createEditorIfNeeded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001502 try {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001503 mEditor.mInputType = mEditor.mKeyListener.getInputType();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001504 } catch (IncompatibleClassChangeError e) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001505 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001506 }
Gilles Debunned7483bf2010-11-10 10:47:45 -08001507 // Change inputType, without affecting transformation.
1508 // No need to applySingleLine since mSingleLine is unchanged.
1509 setInputTypeSingleLine(mSingleLine);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001510 } else {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001511 if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001512 }
1513
1514 InputMethodManager imm = InputMethodManager.peekInstance();
1515 if (imm != null) imm.restartInput(this);
1516 }
1517
1518 private void setKeyListenerOnly(KeyListener input) {
Gilles Debunne60e21862012-01-30 15:04:14 -08001519 if (mEditor == null && input == null) return; // null is the default value
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001520
Gilles Debunne5fae9962012-05-08 14:53:20 -07001521 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001522 if (mEditor.mKeyListener != input) {
1523 mEditor.mKeyListener = input;
Gilles Debunne60e21862012-01-30 15:04:14 -08001524 if (input != null && !(mText instanceof Editable)) {
1525 setText(mText);
1526 }
1527
1528 setFilters((Editable) mText, mFilters);
1529 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001530 }
1531
1532 /**
1533 * @return the movement method being used for this TextView.
1534 * This will frequently be null for non-EditText TextViews.
1535 */
1536 public final MovementMethod getMovementMethod() {
1537 return mMovement;
1538 }
1539
1540 /**
1541 * Sets the movement method (arrow key handler) to be used for
1542 * this TextView. This can be null to disallow using the arrow keys
1543 * to move the cursor or scroll the view.
1544 * <p>
1545 * Be warned that if you want a TextView with a key listener or movement
1546 * method not to be focusable, or if you want a TextView without a
1547 * key listener or movement method to be focusable, you must call
1548 * {@link #setFocusable} again after calling this to get the focusability
1549 * back the way you want it.
1550 */
1551 public final void setMovementMethod(MovementMethod movement) {
Gilles Debunne60e21862012-01-30 15:04:14 -08001552 if (mMovement != movement) {
1553 mMovement = movement;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001554
Gilles Debunne60e21862012-01-30 15:04:14 -08001555 if (movement != null && !(mText instanceof Spannable)) {
1556 setText(mText);
1557 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001558
Gilles Debunne60e21862012-01-30 15:04:14 -08001559 fixFocusableAndClickableSettings();
Gilles Debunnef788a9f2010-07-22 10:17:23 -07001560
Gilles Debunne2d373a12012-04-20 15:32:19 -07001561 // SelectionModifierCursorController depends on textCanBeSelected, which depends on
1562 // mMovement
1563 if (mEditor != null) mEditor.prepareCursorControllers();
Gilles Debunne60e21862012-01-30 15:04:14 -08001564 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001565 }
1566
1567 private void fixFocusableAndClickableSettings() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001568 if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001569 setFocusable(true);
1570 setClickable(true);
1571 setLongClickable(true);
1572 } else {
1573 setFocusable(false);
1574 setClickable(false);
1575 setLongClickable(false);
1576 }
1577 }
1578
1579 /**
1580 * @return the current transformation method for this TextView.
1581 * This will frequently be null except for single-line and password
1582 * fields.
Gilles Debunnef03acef2012-04-30 19:26:19 -07001583 *
1584 * @attr ref android.R.styleable#TextView_password
1585 * @attr ref android.R.styleable#TextView_singleLine
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001586 */
1587 public final TransformationMethod getTransformationMethod() {
1588 return mTransformation;
1589 }
1590
1591 /**
1592 * Sets the transformation that is applied to the text that this
1593 * TextView is displaying.
1594 *
1595 * @attr ref android.R.styleable#TextView_password
1596 * @attr ref android.R.styleable#TextView_singleLine
1597 */
1598 public final void setTransformationMethod(TransformationMethod method) {
1599 if (method == mTransformation) {
1600 // Avoid the setText() below if the transformation is
1601 // the same.
1602 return;
1603 }
1604 if (mTransformation != null) {
1605 if (mText instanceof Spannable) {
1606 ((Spannable) mText).removeSpan(mTransformation);
1607 }
1608 }
1609
1610 mTransformation = method;
1611
Adam Powell7f8f79a2011-07-07 18:35:54 -07001612 if (method instanceof TransformationMethod2) {
1613 TransformationMethod2 method2 = (TransformationMethod2) method;
Gilles Debunne60e21862012-01-30 15:04:14 -08001614 mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable);
Adam Powell7f8f79a2011-07-07 18:35:54 -07001615 method2.setLengthChangesAllowed(mAllowTransformationLengthChange);
1616 } else {
1617 mAllowTransformationLengthChange = false;
1618 }
1619
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001620 setText(mText);
Svetoslav Ganovc406be92012-05-11 16:12:32 -07001621
1622 if (hasPasswordTransformationMethod()) {
1623 notifyAccessibilityStateChanged();
1624 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001625 }
1626
1627 /**
1628 * Returns the top padding of the view, plus space for the top
1629 * Drawable if any.
1630 */
1631 public int getCompoundPaddingTop() {
1632 final Drawables dr = mDrawables;
1633 if (dr == null || dr.mDrawableTop == null) {
1634 return mPaddingTop;
1635 } else {
1636 return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
1637 }
1638 }
1639
1640 /**
1641 * Returns the bottom padding of the view, plus space for the bottom
1642 * Drawable if any.
1643 */
1644 public int getCompoundPaddingBottom() {
1645 final Drawables dr = mDrawables;
1646 if (dr == null || dr.mDrawableBottom == null) {
1647 return mPaddingBottom;
1648 } else {
1649 return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
1650 }
1651 }
1652
1653 /**
1654 * Returns the left padding of the view, plus space for the left
1655 * Drawable if any.
1656 */
1657 public int getCompoundPaddingLeft() {
1658 final Drawables dr = mDrawables;
1659 if (dr == null || dr.mDrawableLeft == null) {
1660 return mPaddingLeft;
1661 } else {
1662 return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
1663 }
1664 }
1665
1666 /**
1667 * Returns the right padding of the view, plus space for the right
1668 * Drawable if any.
1669 */
1670 public int getCompoundPaddingRight() {
1671 final Drawables dr = mDrawables;
1672 if (dr == null || dr.mDrawableRight == null) {
1673 return mPaddingRight;
1674 } else {
1675 return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
1676 }
1677 }
1678
1679 /**
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001680 * Returns the start padding of the view, plus space for the start
1681 * Drawable if any.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001682 */
1683 public int getCompoundPaddingStart() {
1684 resolveDrawables();
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07001685 switch(getLayoutDirection()) {
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001686 default:
1687 case LAYOUT_DIRECTION_LTR:
1688 return getCompoundPaddingLeft();
1689 case LAYOUT_DIRECTION_RTL:
1690 return getCompoundPaddingRight();
1691 }
1692 }
1693
1694 /**
1695 * Returns the end padding of the view, plus space for the end
1696 * Drawable if any.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001697 */
1698 public int getCompoundPaddingEnd() {
1699 resolveDrawables();
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07001700 switch(getLayoutDirection()) {
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001701 default:
1702 case LAYOUT_DIRECTION_LTR:
1703 return getCompoundPaddingRight();
1704 case LAYOUT_DIRECTION_RTL:
1705 return getCompoundPaddingLeft();
1706 }
1707 }
1708
1709 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001710 * Returns the extended top padding of the view, including both the
1711 * top Drawable if any and any extra space to keep more than maxLines
1712 * of text from showing. It is only valid to call this after measuring.
1713 */
1714 public int getExtendedPaddingTop() {
1715 if (mMaxMode != LINES) {
1716 return getCompoundPaddingTop();
1717 }
1718
1719 if (mLayout.getLineCount() <= mMaximum) {
1720 return getCompoundPaddingTop();
1721 }
1722
1723 int top = getCompoundPaddingTop();
1724 int bottom = getCompoundPaddingBottom();
1725 int viewht = getHeight() - top - bottom;
1726 int layoutht = mLayout.getLineTop(mMaximum);
1727
1728 if (layoutht >= viewht) {
1729 return top;
1730 }
1731
1732 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
1733 if (gravity == Gravity.TOP) {
1734 return top;
1735 } else if (gravity == Gravity.BOTTOM) {
1736 return top + viewht - layoutht;
1737 } else { // (gravity == Gravity.CENTER_VERTICAL)
1738 return top + (viewht - layoutht) / 2;
1739 }
1740 }
1741
1742 /**
1743 * Returns the extended bottom padding of the view, including both the
1744 * bottom Drawable if any and any extra space to keep more than maxLines
1745 * of text from showing. It is only valid to call this after measuring.
1746 */
1747 public int getExtendedPaddingBottom() {
1748 if (mMaxMode != LINES) {
1749 return getCompoundPaddingBottom();
1750 }
1751
1752 if (mLayout.getLineCount() <= mMaximum) {
1753 return getCompoundPaddingBottom();
1754 }
1755
1756 int top = getCompoundPaddingTop();
1757 int bottom = getCompoundPaddingBottom();
1758 int viewht = getHeight() - top - bottom;
1759 int layoutht = mLayout.getLineTop(mMaximum);
1760
1761 if (layoutht >= viewht) {
1762 return bottom;
1763 }
1764
1765 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
1766 if (gravity == Gravity.TOP) {
1767 return bottom + viewht - layoutht;
1768 } else if (gravity == Gravity.BOTTOM) {
1769 return bottom;
1770 } else { // (gravity == Gravity.CENTER_VERTICAL)
1771 return bottom + (viewht - layoutht) / 2;
1772 }
1773 }
1774
1775 /**
1776 * Returns the total left padding of the view, including the left
1777 * Drawable if any.
1778 */
1779 public int getTotalPaddingLeft() {
1780 return getCompoundPaddingLeft();
1781 }
1782
1783 /**
1784 * Returns the total right padding of the view, including the right
1785 * Drawable if any.
1786 */
1787 public int getTotalPaddingRight() {
1788 return getCompoundPaddingRight();
1789 }
1790
1791 /**
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001792 * Returns the total start padding of the view, including the start
1793 * Drawable if any.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001794 */
1795 public int getTotalPaddingStart() {
1796 return getCompoundPaddingStart();
1797 }
1798
1799 /**
1800 * Returns the total end padding of the view, including the end
1801 * Drawable if any.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001802 */
1803 public int getTotalPaddingEnd() {
1804 return getCompoundPaddingEnd();
1805 }
1806
1807 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001808 * Returns the total top padding of the view, including the top
1809 * Drawable if any, the extra space to keep more than maxLines
1810 * from showing, and the vertical offset for gravity, if any.
1811 */
1812 public int getTotalPaddingTop() {
1813 return getExtendedPaddingTop() + getVerticalOffset(true);
1814 }
1815
1816 /**
1817 * Returns the total bottom padding of the view, including the bottom
1818 * Drawable if any, the extra space to keep more than maxLines
1819 * from showing, and the vertical offset for gravity, if any.
1820 */
1821 public int getTotalPaddingBottom() {
1822 return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
1823 }
1824
1825 /**
1826 * Sets the Drawables (if any) to appear to the left of, above,
1827 * to the right of, and below the text. Use null if you do not
1828 * want a Drawable there. The Drawables must already have had
1829 * {@link Drawable#setBounds} called.
1830 *
1831 * @attr ref android.R.styleable#TextView_drawableLeft
1832 * @attr ref android.R.styleable#TextView_drawableTop
1833 * @attr ref android.R.styleable#TextView_drawableRight
1834 * @attr ref android.R.styleable#TextView_drawableBottom
1835 */
1836 public void setCompoundDrawables(Drawable left, Drawable top,
1837 Drawable right, Drawable bottom) {
1838 Drawables dr = mDrawables;
1839
1840 final boolean drawables = left != null || top != null
1841 || right != null || bottom != null;
1842
1843 if (!drawables) {
1844 // Clearing drawables... can we free the data structure?
1845 if (dr != null) {
1846 if (dr.mDrawablePadding == 0) {
1847 mDrawables = null;
1848 } else {
1849 // We need to retain the last set padding, so just clear
1850 // out all of the fields in the existing structure.
Romain Guy48540eb2009-05-19 16:44:57 -07001851 if (dr.mDrawableLeft != null) dr.mDrawableLeft.setCallback(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001852 dr.mDrawableLeft = null;
Romain Guy48540eb2009-05-19 16:44:57 -07001853 if (dr.mDrawableTop != null) dr.mDrawableTop.setCallback(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001854 dr.mDrawableTop = null;
Romain Guy48540eb2009-05-19 16:44:57 -07001855 if (dr.mDrawableRight != null) dr.mDrawableRight.setCallback(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001856 dr.mDrawableRight = null;
Romain Guy48540eb2009-05-19 16:44:57 -07001857 if (dr.mDrawableBottom != null) dr.mDrawableBottom.setCallback(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001858 dr.mDrawableBottom = null;
1859 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
1860 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
1861 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
1862 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
1863 }
1864 }
1865 } else {
1866 if (dr == null) {
1867 mDrawables = dr = new Drawables();
1868 }
1869
Romain Guy48540eb2009-05-19 16:44:57 -07001870 if (dr.mDrawableLeft != left && dr.mDrawableLeft != null) {
1871 dr.mDrawableLeft.setCallback(null);
1872 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001873 dr.mDrawableLeft = left;
Romain Guy8e618e52010-03-08 12:18:20 -08001874
1875 if (dr.mDrawableTop != top && dr.mDrawableTop != null) {
Romain Guy48540eb2009-05-19 16:44:57 -07001876 dr.mDrawableTop.setCallback(null);
1877 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001878 dr.mDrawableTop = top;
Romain Guy8e618e52010-03-08 12:18:20 -08001879
1880 if (dr.mDrawableRight != right && dr.mDrawableRight != null) {
Romain Guy48540eb2009-05-19 16:44:57 -07001881 dr.mDrawableRight.setCallback(null);
1882 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001883 dr.mDrawableRight = right;
Romain Guy8e618e52010-03-08 12:18:20 -08001884
1885 if (dr.mDrawableBottom != bottom && dr.mDrawableBottom != null) {
Romain Guy48540eb2009-05-19 16:44:57 -07001886 dr.mDrawableBottom.setCallback(null);
1887 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001888 dr.mDrawableBottom = bottom;
1889
1890 final Rect compoundRect = dr.mCompoundRect;
Romain Guy48540eb2009-05-19 16:44:57 -07001891 int[] state;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001892
1893 state = getDrawableState();
1894
1895 if (left != null) {
1896 left.setState(state);
1897 left.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07001898 left.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001899 dr.mDrawableSizeLeft = compoundRect.width();
1900 dr.mDrawableHeightLeft = compoundRect.height();
1901 } else {
1902 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
1903 }
1904
1905 if (right != null) {
1906 right.setState(state);
1907 right.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07001908 right.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001909 dr.mDrawableSizeRight = compoundRect.width();
1910 dr.mDrawableHeightRight = compoundRect.height();
1911 } else {
1912 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
1913 }
1914
1915 if (top != null) {
1916 top.setState(state);
1917 top.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07001918 top.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001919 dr.mDrawableSizeTop = compoundRect.height();
1920 dr.mDrawableWidthTop = compoundRect.width();
1921 } else {
1922 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
1923 }
1924
1925 if (bottom != null) {
1926 bottom.setState(state);
1927 bottom.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07001928 bottom.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001929 dr.mDrawableSizeBottom = compoundRect.height();
1930 dr.mDrawableWidthBottom = compoundRect.width();
1931 } else {
1932 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
1933 }
1934 }
1935
1936 invalidate();
1937 requestLayout();
1938 }
1939
1940 /**
1941 * Sets the Drawables (if any) to appear to the left of, above,
1942 * to the right of, and below the text. Use 0 if you do not
1943 * want a Drawable there. The Drawables' bounds will be set to
1944 * their intrinsic bounds.
1945 *
1946 * @param left Resource identifier of the left Drawable.
1947 * @param top Resource identifier of the top Drawable.
1948 * @param right Resource identifier of the right Drawable.
1949 * @param bottom Resource identifier of the bottom Drawable.
1950 *
1951 * @attr ref android.R.styleable#TextView_drawableLeft
1952 * @attr ref android.R.styleable#TextView_drawableTop
1953 * @attr ref android.R.styleable#TextView_drawableRight
1954 * @attr ref android.R.styleable#TextView_drawableBottom
1955 */
Daniel Sandler820ba322012-03-23 16:36:00 -05001956 @android.view.RemotableViewMethod
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001957 public void setCompoundDrawablesWithIntrinsicBounds(int left, int top, int right, int bottom) {
1958 final Resources resources = getContext().getResources();
1959 setCompoundDrawablesWithIntrinsicBounds(left != 0 ? resources.getDrawable(left) : null,
1960 top != 0 ? resources.getDrawable(top) : null,
1961 right != 0 ? resources.getDrawable(right) : null,
1962 bottom != 0 ? resources.getDrawable(bottom) : null);
1963 }
1964
1965 /**
1966 * Sets the Drawables (if any) to appear to the left of, above,
1967 * to the right of, and below the text. Use null if you do not
1968 * want a Drawable there. The Drawables' bounds will be set to
1969 * their intrinsic bounds.
1970 *
1971 * @attr ref android.R.styleable#TextView_drawableLeft
1972 * @attr ref android.R.styleable#TextView_drawableTop
1973 * @attr ref android.R.styleable#TextView_drawableRight
1974 * @attr ref android.R.styleable#TextView_drawableBottom
1975 */
1976 public void setCompoundDrawablesWithIntrinsicBounds(Drawable left, Drawable top,
1977 Drawable right, Drawable bottom) {
1978
1979 if (left != null) {
1980 left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
1981 }
1982 if (right != null) {
1983 right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
1984 }
1985 if (top != null) {
1986 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
1987 }
1988 if (bottom != null) {
1989 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
1990 }
1991 setCompoundDrawables(left, top, right, bottom);
1992 }
1993
1994 /**
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001995 * Sets the Drawables (if any) to appear to the start of, above,
1996 * to the end of, and below the text. Use null if you do not
1997 * want a Drawable there. The Drawables must already have had
1998 * {@link Drawable#setBounds} called.
1999 *
2000 * @attr ref android.R.styleable#TextView_drawableStart
2001 * @attr ref android.R.styleable#TextView_drawableTop
2002 * @attr ref android.R.styleable#TextView_drawableEnd
2003 * @attr ref android.R.styleable#TextView_drawableBottom
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002004 */
2005 public void setCompoundDrawablesRelative(Drawable start, Drawable top,
2006 Drawable end, Drawable bottom) {
2007 Drawables dr = mDrawables;
2008
2009 final boolean drawables = start != null || top != null
2010 || end != null || bottom != null;
2011
2012 if (!drawables) {
2013 // Clearing drawables... can we free the data structure?
2014 if (dr != null) {
2015 if (dr.mDrawablePadding == 0) {
2016 mDrawables = null;
2017 } else {
2018 // We need to retain the last set padding, so just clear
2019 // out all of the fields in the existing structure.
2020 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
2021 dr.mDrawableStart = null;
2022 if (dr.mDrawableTop != null) dr.mDrawableTop.setCallback(null);
2023 dr.mDrawableTop = null;
2024 if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null);
2025 dr.mDrawableEnd = null;
2026 if (dr.mDrawableBottom != null) dr.mDrawableBottom.setCallback(null);
2027 dr.mDrawableBottom = null;
2028 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2029 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2030 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2031 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2032 }
2033 }
2034 } else {
2035 if (dr == null) {
2036 mDrawables = dr = new Drawables();
2037 }
2038
2039 if (dr.mDrawableStart != start && dr.mDrawableStart != null) {
2040 dr.mDrawableStart.setCallback(null);
2041 }
2042 dr.mDrawableStart = start;
2043
2044 if (dr.mDrawableTop != top && dr.mDrawableTop != null) {
2045 dr.mDrawableTop.setCallback(null);
2046 }
2047 dr.mDrawableTop = top;
2048
2049 if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) {
2050 dr.mDrawableEnd.setCallback(null);
2051 }
2052 dr.mDrawableEnd = end;
2053
2054 if (dr.mDrawableBottom != bottom && dr.mDrawableBottom != null) {
2055 dr.mDrawableBottom.setCallback(null);
2056 }
2057 dr.mDrawableBottom = bottom;
2058
2059 final Rect compoundRect = dr.mCompoundRect;
2060 int[] state;
2061
2062 state = getDrawableState();
2063
2064 if (start != null) {
2065 start.setState(state);
2066 start.copyBounds(compoundRect);
2067 start.setCallback(this);
2068 dr.mDrawableSizeStart = compoundRect.width();
2069 dr.mDrawableHeightStart = compoundRect.height();
2070 } else {
2071 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2072 }
2073
2074 if (end != null) {
2075 end.setState(state);
2076 end.copyBounds(compoundRect);
2077 end.setCallback(this);
2078 dr.mDrawableSizeEnd = compoundRect.width();
2079 dr.mDrawableHeightEnd = compoundRect.height();
2080 } else {
2081 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2082 }
2083
2084 if (top != null) {
2085 top.setState(state);
2086 top.copyBounds(compoundRect);
2087 top.setCallback(this);
2088 dr.mDrawableSizeTop = compoundRect.height();
2089 dr.mDrawableWidthTop = compoundRect.width();
2090 } else {
2091 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2092 }
2093
2094 if (bottom != null) {
2095 bottom.setState(state);
2096 bottom.copyBounds(compoundRect);
2097 bottom.setCallback(this);
2098 dr.mDrawableSizeBottom = compoundRect.height();
2099 dr.mDrawableWidthBottom = compoundRect.width();
2100 } else {
2101 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2102 }
2103 }
2104
2105 resolveDrawables();
2106 invalidate();
2107 requestLayout();
2108 }
2109
2110 /**
2111 * Sets the Drawables (if any) to appear to the start of, above,
2112 * to the end of, and below the text. Use 0 if you do not
2113 * want a Drawable there. The Drawables' bounds will be set to
2114 * their intrinsic bounds.
2115 *
2116 * @param start Resource identifier of the start Drawable.
2117 * @param top Resource identifier of the top Drawable.
2118 * @param end Resource identifier of the end Drawable.
2119 * @param bottom Resource identifier of the bottom Drawable.
2120 *
2121 * @attr ref android.R.styleable#TextView_drawableStart
2122 * @attr ref android.R.styleable#TextView_drawableTop
2123 * @attr ref android.R.styleable#TextView_drawableEnd
2124 * @attr ref android.R.styleable#TextView_drawableBottom
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002125 */
Daniel Sandler820ba322012-03-23 16:36:00 -05002126 @android.view.RemotableViewMethod
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002127 public void setCompoundDrawablesRelativeWithIntrinsicBounds(int start, int top, int end,
2128 int bottom) {
2129 resetResolvedDrawables();
2130 final Resources resources = getContext().getResources();
2131 setCompoundDrawablesRelativeWithIntrinsicBounds(
2132 start != 0 ? resources.getDrawable(start) : null,
2133 top != 0 ? resources.getDrawable(top) : null,
2134 end != 0 ? resources.getDrawable(end) : null,
2135 bottom != 0 ? resources.getDrawable(bottom) : null);
2136 }
2137
2138 /**
2139 * Sets the Drawables (if any) to appear to the start of, above,
2140 * to the end of, and below the text. Use null if you do not
2141 * want a Drawable there. The Drawables' bounds will be set to
2142 * their intrinsic bounds.
2143 *
2144 * @attr ref android.R.styleable#TextView_drawableStart
2145 * @attr ref android.R.styleable#TextView_drawableTop
2146 * @attr ref android.R.styleable#TextView_drawableEnd
2147 * @attr ref android.R.styleable#TextView_drawableBottom
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002148 */
2149 public void setCompoundDrawablesRelativeWithIntrinsicBounds(Drawable start, Drawable top,
2150 Drawable end, Drawable bottom) {
2151
2152 resetResolvedDrawables();
2153 if (start != null) {
2154 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
2155 }
2156 if (end != null) {
2157 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
2158 }
2159 if (top != null) {
2160 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
2161 }
2162 if (bottom != null) {
2163 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
2164 }
2165 setCompoundDrawablesRelative(start, top, end, bottom);
2166 }
2167
2168 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002169 * Returns drawables for the left, top, right, and bottom borders.
Gilles Debunnef03acef2012-04-30 19:26:19 -07002170 *
2171 * @attr ref android.R.styleable#TextView_drawableLeft
2172 * @attr ref android.R.styleable#TextView_drawableTop
2173 * @attr ref android.R.styleable#TextView_drawableRight
2174 * @attr ref android.R.styleable#TextView_drawableBottom
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002175 */
2176 public Drawable[] getCompoundDrawables() {
2177 final Drawables dr = mDrawables;
2178 if (dr != null) {
2179 return new Drawable[] {
2180 dr.mDrawableLeft, dr.mDrawableTop, dr.mDrawableRight, dr.mDrawableBottom
2181 };
2182 } else {
2183 return new Drawable[] { null, null, null, null };
2184 }
2185 }
2186
2187 /**
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002188 * Returns drawables for the start, top, end, and bottom borders.
Gilles Debunnef03acef2012-04-30 19:26:19 -07002189 *
2190 * @attr ref android.R.styleable#TextView_drawableStart
2191 * @attr ref android.R.styleable#TextView_drawableTop
2192 * @attr ref android.R.styleable#TextView_drawableEnd
2193 * @attr ref android.R.styleable#TextView_drawableBottom
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002194 */
2195 public Drawable[] getCompoundDrawablesRelative() {
2196 final Drawables dr = mDrawables;
2197 if (dr != null) {
2198 return new Drawable[] {
2199 dr.mDrawableStart, dr.mDrawableTop, dr.mDrawableEnd, dr.mDrawableBottom
2200 };
2201 } else {
2202 return new Drawable[] { null, null, null, null };
2203 }
2204 }
2205
2206 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002207 * Sets the size of the padding between the compound drawables and
2208 * the text.
2209 *
2210 * @attr ref android.R.styleable#TextView_drawablePadding
2211 */
Daniel Sandler820ba322012-03-23 16:36:00 -05002212 @android.view.RemotableViewMethod
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002213 public void setCompoundDrawablePadding(int pad) {
2214 Drawables dr = mDrawables;
2215 if (pad == 0) {
2216 if (dr != null) {
2217 dr.mDrawablePadding = pad;
2218 }
2219 } else {
2220 if (dr == null) {
2221 mDrawables = dr = new Drawables();
2222 }
2223 dr.mDrawablePadding = pad;
2224 }
2225
2226 invalidate();
2227 requestLayout();
2228 }
2229
2230 /**
2231 * Returns the padding between the compound drawables and the text.
Gilles Debunnef03acef2012-04-30 19:26:19 -07002232 *
2233 * @attr ref android.R.styleable#TextView_drawablePadding
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002234 */
2235 public int getCompoundDrawablePadding() {
2236 final Drawables dr = mDrawables;
2237 return dr != null ? dr.mDrawablePadding : 0;
2238 }
2239
2240 @Override
2241 public void setPadding(int left, int top, int right, int bottom) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07002242 if (left != mPaddingLeft ||
2243 right != mPaddingRight ||
2244 top != mPaddingTop ||
2245 bottom != mPaddingBottom) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002246 nullLayouts();
2247 }
2248
2249 // the super call will requestLayout()
2250 super.setPadding(left, top, right, bottom);
2251 invalidate();
2252 }
2253
Fabrice Di Megliobf923eb2012-03-07 16:20:22 -08002254 @Override
2255 public void setPaddingRelative(int start, int top, int end, int bottom) {
2256 if (start != getPaddingStart() ||
2257 end != getPaddingEnd() ||
2258 top != mPaddingTop ||
2259 bottom != mPaddingBottom) {
2260 nullLayouts();
2261 }
2262
2263 // the super call will requestLayout()
2264 super.setPaddingRelative(start, top, end, bottom);
2265 invalidate();
2266 }
2267
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002268 /**
2269 * Gets the autolink mask of the text. See {@link
2270 * android.text.util.Linkify#ALL Linkify.ALL} and peers for
2271 * possible values.
2272 *
2273 * @attr ref android.R.styleable#TextView_autoLink
2274 */
2275 public final int getAutoLinkMask() {
2276 return mAutoLinkMask;
2277 }
2278
2279 /**
2280 * Sets the text color, size, style, hint color, and highlight color
2281 * from the specified TextAppearance resource.
2282 */
2283 public void setTextAppearance(Context context, int resid) {
2284 TypedArray appearance =
2285 context.obtainStyledAttributes(resid,
2286 com.android.internal.R.styleable.TextAppearance);
2287
2288 int color;
2289 ColorStateList colors;
2290 int ts;
2291
Gilles Debunne2d373a12012-04-20 15:32:19 -07002292 color = appearance.getColor(
2293 com.android.internal.R.styleable.TextAppearance_textColorHighlight, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002294 if (color != 0) {
2295 setHighlightColor(color);
2296 }
2297
2298 colors = appearance.getColorStateList(com.android.internal.R.styleable.
2299 TextAppearance_textColor);
2300 if (colors != null) {
2301 setTextColor(colors);
2302 }
2303
2304 ts = appearance.getDimensionPixelSize(com.android.internal.R.styleable.
2305 TextAppearance_textSize, 0);
2306 if (ts != 0) {
2307 setRawTextSize(ts);
2308 }
2309
2310 colors = appearance.getColorStateList(com.android.internal.R.styleable.
2311 TextAppearance_textColorHint);
2312 if (colors != null) {
2313 setHintTextColor(colors);
2314 }
2315
2316 colors = appearance.getColorStateList(com.android.internal.R.styleable.
2317 TextAppearance_textColorLink);
2318 if (colors != null) {
2319 setLinkTextColor(colors);
2320 }
2321
Raph Leviend570e892012-05-09 11:45:34 -07002322 String familyName;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002323 int typefaceIndex, styleIndex;
2324
Raph Leviend570e892012-05-09 11:45:34 -07002325 familyName = appearance.getString(com.android.internal.R.styleable.
2326 TextAppearance_fontFamily);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002327 typefaceIndex = appearance.getInt(com.android.internal.R.styleable.
2328 TextAppearance_typeface, -1);
2329 styleIndex = appearance.getInt(com.android.internal.R.styleable.
2330 TextAppearance_textStyle, -1);
2331
Raph Leviend570e892012-05-09 11:45:34 -07002332 setTypefaceFromAttrs(familyName, typefaceIndex, styleIndex);
Gilles Debunne21078e42011-08-02 10:22:35 -07002333
Adam Powell7f8f79a2011-07-07 18:35:54 -07002334 if (appearance.getBoolean(com.android.internal.R.styleable.TextAppearance_textAllCaps,
2335 false)) {
2336 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
2337 }
2338
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002339 appearance.recycle();
2340 }
2341
2342 /**
Victoria Leasedf8ef4b2012-08-17 15:34:01 -07002343 * Get the default {@link Locale} of the text in this TextView.
2344 * @return the default {@link Locale} of the text in this TextView.
2345 */
2346 public Locale getTextLocale() {
2347 return mTextPaint.getTextLocale();
2348 }
2349
2350 /**
2351 * Set the default {@link Locale} of the text in this TextView to the given value. This value
2352 * is used to choose appropriate typefaces for ambiguous characters. Typically used for CJK
2353 * locales to disambiguate Hanzi/Kanji/Hanja characters.
2354 *
2355 * @param locale the {@link Locale} for drawing text, must not be null.
2356 *
2357 * @see Paint#setTextLocale
2358 */
2359 public void setTextLocale(Locale locale) {
2360 mTextPaint.setTextLocale(locale);
2361 }
2362
2363 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002364 * @return the size (in pixels) of the default text size in this TextView.
2365 */
Fabrice Di Meglioc54da1c2012-04-27 16:16:35 -07002366 @ViewDebug.ExportedProperty(category = "text")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002367 public float getTextSize() {
2368 return mTextPaint.getTextSize();
2369 }
2370
2371 /**
2372 * Set the default text size to the given value, interpreted as "scaled
2373 * pixel" units. This size is adjusted based on the current density and
2374 * user font size preference.
2375 *
2376 * @param size The scaled pixel size.
2377 *
2378 * @attr ref android.R.styleable#TextView_textSize
2379 */
2380 @android.view.RemotableViewMethod
2381 public void setTextSize(float size) {
2382 setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
2383 }
2384
2385 /**
2386 * Set the default text size to a given unit and value. See {@link
2387 * TypedValue} for the possible dimension units.
2388 *
2389 * @param unit The desired dimension unit.
2390 * @param size The desired size in the given units.
2391 *
2392 * @attr ref android.R.styleable#TextView_textSize
2393 */
2394 public void setTextSize(int unit, float size) {
2395 Context c = getContext();
2396 Resources r;
2397
2398 if (c == null)
2399 r = Resources.getSystem();
2400 else
2401 r = c.getResources();
2402
2403 setRawTextSize(TypedValue.applyDimension(
2404 unit, size, r.getDisplayMetrics()));
2405 }
2406
2407 private void setRawTextSize(float size) {
2408 if (size != mTextPaint.getTextSize()) {
2409 mTextPaint.setTextSize(size);
2410
2411 if (mLayout != null) {
2412 nullLayouts();
2413 requestLayout();
2414 invalidate();
2415 }
2416 }
2417 }
2418
2419 /**
2420 * @return the extent by which text is currently being stretched
2421 * horizontally. This will usually be 1.
2422 */
2423 public float getTextScaleX() {
2424 return mTextPaint.getTextScaleX();
2425 }
2426
2427 /**
2428 * Sets the extent by which text should be stretched horizontally.
2429 *
2430 * @attr ref android.R.styleable#TextView_textScaleX
2431 */
2432 @android.view.RemotableViewMethod
2433 public void setTextScaleX(float size) {
2434 if (size != mTextPaint.getTextScaleX()) {
Romain Guy939151f2009-04-08 14:22:40 -07002435 mUserSetTextScaleX = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002436 mTextPaint.setTextScaleX(size);
2437
2438 if (mLayout != null) {
2439 nullLayouts();
2440 requestLayout();
2441 invalidate();
2442 }
2443 }
2444 }
2445
2446 /**
2447 * Sets the typeface and style in which the text should be displayed.
2448 * Note that not all Typeface families actually have bold and italic
2449 * variants, so you may need to use
2450 * {@link #setTypeface(Typeface, int)} to get the appearance
2451 * that you actually want.
2452 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002453 * @see #getTypeface()
2454 *
Raph Leviend570e892012-05-09 11:45:34 -07002455 * @attr ref android.R.styleable#TextView_fontFamily
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002456 * @attr ref android.R.styleable#TextView_typeface
2457 * @attr ref android.R.styleable#TextView_textStyle
2458 */
2459 public void setTypeface(Typeface tf) {
2460 if (mTextPaint.getTypeface() != tf) {
2461 mTextPaint.setTypeface(tf);
2462
2463 if (mLayout != null) {
2464 nullLayouts();
2465 requestLayout();
2466 invalidate();
2467 }
2468 }
2469 }
2470
2471 /**
2472 * @return the current typeface and style in which the text is being
2473 * displayed.
Gilles Debunnef03acef2012-04-30 19:26:19 -07002474 *
2475 * @see #setTypeface(Typeface)
2476 *
Raph Leviend570e892012-05-09 11:45:34 -07002477 * @attr ref android.R.styleable#TextView_fontFamily
Gilles Debunnef03acef2012-04-30 19:26:19 -07002478 * @attr ref android.R.styleable#TextView_typeface
2479 * @attr ref android.R.styleable#TextView_textStyle
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002480 */
2481 public Typeface getTypeface() {
2482 return mTextPaint.getTypeface();
2483 }
2484
2485 /**
2486 * Sets the text color for all the states (normal, selected,
2487 * focused) to be this color.
2488 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002489 * @see #setTextColor(ColorStateList)
2490 * @see #getTextColors()
2491 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002492 * @attr ref android.R.styleable#TextView_textColor
2493 */
2494 @android.view.RemotableViewMethod
2495 public void setTextColor(int color) {
2496 mTextColor = ColorStateList.valueOf(color);
2497 updateTextColors();
2498 }
2499
2500 /**
2501 * Sets the text color.
2502 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002503 * @see #setTextColor(int)
2504 * @see #getTextColors()
2505 * @see #setHintTextColor(ColorStateList)
2506 * @see #setLinkTextColor(ColorStateList)
2507 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002508 * @attr ref android.R.styleable#TextView_textColor
2509 */
2510 public void setTextColor(ColorStateList colors) {
2511 if (colors == null) {
2512 throw new NullPointerException();
2513 }
2514
2515 mTextColor = colors;
2516 updateTextColors();
2517 }
2518
2519 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002520 * Gets the text colors for the different states (normal, selected, focused) of the TextView.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002521 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002522 * @see #setTextColor(ColorStateList)
2523 * @see #setTextColor(int)
2524 *
2525 * @attr ref android.R.styleable#TextView_textColor
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002526 */
2527 public final ColorStateList getTextColors() {
2528 return mTextColor;
2529 }
2530
2531 /**
2532 * <p>Return the current color selected for normal text.</p>
2533 *
2534 * @return Returns the current text color.
2535 */
2536 public final int getCurrentTextColor() {
2537 return mCurTextColor;
2538 }
2539
2540 /**
2541 * Sets the color used to display the selection highlight.
2542 *
2543 * @attr ref android.R.styleable#TextView_textColorHighlight
2544 */
2545 @android.view.RemotableViewMethod
2546 public void setHighlightColor(int color) {
2547 if (mHighlightColor != color) {
2548 mHighlightColor = color;
2549 invalidate();
2550 }
2551 }
2552
2553 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002554 * @return the color used to display the selection highlight
2555 *
2556 * @see #setHighlightColor(int)
2557 *
2558 * @attr ref android.R.styleable#TextView_textColorHighlight
2559 */
2560 public int getHighlightColor() {
2561 return mHighlightColor;
2562 }
2563
2564 /**
Gilles Debunne3473b2b2012-04-20 16:21:10 -07002565 * Sets whether the soft input method will be made visible when this
2566 * TextView gets focused. The default is true.
2567 * @hide
2568 */
2569 @android.view.RemotableViewMethod
2570 public final void setShowSoftInputOnFocus(boolean show) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07002571 createEditorIfNeeded();
Gilles Debunne3473b2b2012-04-20 16:21:10 -07002572 mEditor.mShowSoftInputOnFocus = show;
2573 }
2574
2575 /**
2576 * Returns whether the soft input method will be made visible when this
2577 * TextView gets focused. The default is true.
2578 * @hide
2579 */
2580 public final boolean getShowSoftInputOnFocus() {
2581 // When there is no Editor, return default true value
2582 return mEditor == null || mEditor.mShowSoftInputOnFocus;
2583 }
2584
2585 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002586 * Gives the text a shadow of the specified radius and color, the specified
2587 * distance from its normal position.
2588 *
2589 * @attr ref android.R.styleable#TextView_shadowColor
2590 * @attr ref android.R.styleable#TextView_shadowDx
2591 * @attr ref android.R.styleable#TextView_shadowDy
2592 * @attr ref android.R.styleable#TextView_shadowRadius
2593 */
2594 public void setShadowLayer(float radius, float dx, float dy, int color) {
2595 mTextPaint.setShadowLayer(radius, dx, dy, color);
2596
2597 mShadowRadius = radius;
2598 mShadowDx = dx;
2599 mShadowDy = dy;
2600
Gilles Debunne33b7de852012-03-12 11:57:48 -07002601 // Will change text clip region
Gilles Debunne2d373a12012-04-20 15:32:19 -07002602 if (mEditor != null) mEditor.invalidateTextDisplayList();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002603 invalidate();
2604 }
2605
2606 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002607 * Gets the radius of the shadow layer.
2608 *
2609 * @return the radius of the shadow layer. If 0, the shadow layer is not visible
2610 *
2611 * @see #setShadowLayer(float, float, float, int)
2612 *
2613 * @attr ref android.R.styleable#TextView_shadowRadius
2614 */
2615 public float getShadowRadius() {
2616 return mShadowRadius;
2617 }
2618
2619 /**
2620 * @return the horizontal offset of the shadow layer
2621 *
2622 * @see #setShadowLayer(float, float, float, int)
2623 *
2624 * @attr ref android.R.styleable#TextView_shadowDx
2625 */
2626 public float getShadowDx() {
2627 return mShadowDx;
2628 }
2629
2630 /**
2631 * @return the vertical offset of the shadow layer
2632 *
2633 * @see #setShadowLayer(float, float, float, int)
2634 *
2635 * @attr ref android.R.styleable#TextView_shadowDy
2636 */
2637 public float getShadowDy() {
2638 return mShadowDy;
2639 }
2640
2641 /**
2642 * @return the color of the shadow layer
2643 *
2644 * @see #setShadowLayer(float, float, float, int)
2645 *
2646 * @attr ref android.R.styleable#TextView_shadowColor
2647 */
2648 public int getShadowColor() {
2649 return mTextPaint.shadowColor;
2650 }
2651
2652 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002653 * @return the base paint used for the text. Please use this only to
2654 * consult the Paint's properties and not to change them.
2655 */
2656 public TextPaint getPaint() {
2657 return mTextPaint;
2658 }
2659
2660 /**
2661 * Sets the autolink mask of the text. See {@link
2662 * android.text.util.Linkify#ALL Linkify.ALL} and peers for
2663 * possible values.
2664 *
2665 * @attr ref android.R.styleable#TextView_autoLink
2666 */
2667 @android.view.RemotableViewMethod
2668 public final void setAutoLinkMask(int mask) {
2669 mAutoLinkMask = mask;
2670 }
2671
2672 /**
2673 * Sets whether the movement method will automatically be set to
2674 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
2675 * set to nonzero and links are detected in {@link #setText}.
2676 * The default is true.
2677 *
2678 * @attr ref android.R.styleable#TextView_linksClickable
2679 */
2680 @android.view.RemotableViewMethod
2681 public final void setLinksClickable(boolean whether) {
2682 mLinksClickable = whether;
2683 }
2684
2685 /**
2686 * Returns whether the movement method will automatically be set to
2687 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
2688 * set to nonzero and links are detected in {@link #setText}.
2689 * The default is true.
2690 *
2691 * @attr ref android.R.styleable#TextView_linksClickable
2692 */
2693 public final boolean getLinksClickable() {
2694 return mLinksClickable;
2695 }
2696
2697 /**
2698 * Returns the list of URLSpans attached to the text
2699 * (by {@link Linkify} or otherwise) if any. You can call
2700 * {@link URLSpan#getURL} on them to find where they link to
2701 * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
2702 * to find the region of the text they are attached to.
2703 */
2704 public URLSpan[] getUrls() {
2705 if (mText instanceof Spanned) {
2706 return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
2707 } else {
2708 return new URLSpan[0];
2709 }
2710 }
2711
2712 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002713 * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this
2714 * TextView.
2715 *
2716 * @see #setHintTextColor(ColorStateList)
2717 * @see #getHintTextColors()
2718 * @see #setTextColor(int)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002719 *
2720 * @attr ref android.R.styleable#TextView_textColorHint
2721 */
2722 @android.view.RemotableViewMethod
2723 public final void setHintTextColor(int color) {
2724 mHintTextColor = ColorStateList.valueOf(color);
2725 updateTextColors();
2726 }
2727
2728 /**
2729 * Sets the color of the hint text.
2730 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002731 * @see #getHintTextColors()
2732 * @see #setHintTextColor(int)
2733 * @see #setTextColor(ColorStateList)
2734 * @see #setLinkTextColor(ColorStateList)
2735 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002736 * @attr ref android.R.styleable#TextView_textColorHint
2737 */
2738 public final void setHintTextColor(ColorStateList colors) {
2739 mHintTextColor = colors;
2740 updateTextColors();
2741 }
2742
2743 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002744 * @return the color of the hint text, for the different states of this TextView.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002745 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002746 * @see #setHintTextColor(ColorStateList)
2747 * @see #setHintTextColor(int)
2748 * @see #setTextColor(ColorStateList)
2749 * @see #setLinkTextColor(ColorStateList)
2750 *
2751 * @attr ref android.R.styleable#TextView_textColorHint
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002752 */
2753 public final ColorStateList getHintTextColors() {
2754 return mHintTextColor;
2755 }
2756
2757 /**
2758 * <p>Return the current color selected to paint the hint text.</p>
2759 *
2760 * @return Returns the current hint text color.
2761 */
2762 public final int getCurrentHintTextColor() {
2763 return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
2764 }
2765
2766 /**
2767 * Sets the color of links in the text.
2768 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002769 * @see #setLinkTextColor(ColorStateList)
2770 * @see #getLinkTextColors()
2771 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002772 * @attr ref android.R.styleable#TextView_textColorLink
2773 */
2774 @android.view.RemotableViewMethod
2775 public final void setLinkTextColor(int color) {
2776 mLinkTextColor = ColorStateList.valueOf(color);
2777 updateTextColors();
2778 }
2779
2780 /**
2781 * Sets the color of links in the text.
2782 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002783 * @see #setLinkTextColor(int)
2784 * @see #getLinkTextColors()
2785 * @see #setTextColor(ColorStateList)
2786 * @see #setHintTextColor(ColorStateList)
2787 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002788 * @attr ref android.R.styleable#TextView_textColorLink
2789 */
2790 public final void setLinkTextColor(ColorStateList colors) {
2791 mLinkTextColor = colors;
2792 updateTextColors();
2793 }
2794
2795 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002796 * @return the list of colors used to paint the links in the text, for the different states of
2797 * this TextView
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002798 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002799 * @see #setLinkTextColor(ColorStateList)
2800 * @see #setLinkTextColor(int)
2801 *
2802 * @attr ref android.R.styleable#TextView_textColorLink
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002803 */
2804 public final ColorStateList getLinkTextColors() {
2805 return mLinkTextColor;
2806 }
2807
2808 /**
2809 * Sets the horizontal alignment of the text and the
2810 * vertical gravity that will be used when there is extra space
2811 * in the TextView beyond what is required for the text itself.
2812 *
2813 * @see android.view.Gravity
2814 * @attr ref android.R.styleable#TextView_gravity
2815 */
2816 public void setGravity(int gravity) {
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07002817 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
Fabrice Di Meglio9e3b0022011-06-06 16:30:29 -07002818 gravity |= Gravity.START;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002819 }
2820 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
2821 gravity |= Gravity.TOP;
2822 }
2823
2824 boolean newLayout = false;
2825
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07002826 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) !=
2827 (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002828 newLayout = true;
2829 }
2830
2831 if (gravity != mGravity) {
2832 invalidate();
Fabrice Di Meglio9f513842011-10-12 11:43:27 -07002833 mLayoutAlignment = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002834 }
2835
2836 mGravity = gravity;
2837
2838 if (mLayout != null && newLayout) {
2839 // XXX this is heavy-handed because no actual content changes.
2840 int want = mLayout.getWidth();
2841 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
2842
2843 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
2844 mRight - mLeft - getCompoundPaddingLeft() -
2845 getCompoundPaddingRight(), true);
2846 }
2847 }
2848
2849 /**
2850 * Returns the horizontal and vertical alignment of this TextView.
2851 *
2852 * @see android.view.Gravity
2853 * @attr ref android.R.styleable#TextView_gravity
2854 */
2855 public int getGravity() {
2856 return mGravity;
2857 }
2858
2859 /**
2860 * @return the flags on the Paint being used to display the text.
2861 * @see Paint#getFlags
2862 */
2863 public int getPaintFlags() {
2864 return mTextPaint.getFlags();
2865 }
2866
2867 /**
2868 * Sets flags on the Paint being used to display the text and
2869 * reflows the text if they are different from the old flags.
2870 * @see Paint#setFlags
2871 */
2872 @android.view.RemotableViewMethod
2873 public void setPaintFlags(int flags) {
2874 if (mTextPaint.getFlags() != flags) {
2875 mTextPaint.setFlags(flags);
2876
2877 if (mLayout != null) {
2878 nullLayouts();
2879 requestLayout();
2880 invalidate();
2881 }
2882 }
2883 }
2884
2885 /**
2886 * Sets whether the text should be allowed to be wider than the
2887 * View is. If false, it will be wrapped to the width of the View.
2888 *
2889 * @attr ref android.R.styleable#TextView_scrollHorizontally
2890 */
2891 public void setHorizontallyScrolling(boolean whether) {
Gilles Debunne22378292011-08-12 10:38:52 -07002892 if (mHorizontallyScrolling != whether) {
2893 mHorizontallyScrolling = whether;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002894
Gilles Debunne22378292011-08-12 10:38:52 -07002895 if (mLayout != null) {
2896 nullLayouts();
2897 requestLayout();
2898 invalidate();
2899 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002900 }
2901 }
2902
2903 /**
Gilles Debunnef2a02012011-10-27 11:10:14 -07002904 * Returns whether the text is allowed to be wider than the View is.
2905 * If false, the text will be wrapped to the width of the View.
2906 *
2907 * @attr ref android.R.styleable#TextView_scrollHorizontally
2908 * @hide
2909 */
2910 public boolean getHorizontallyScrolling() {
2911 return mHorizontallyScrolling;
2912 }
2913
2914 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08002915 * Makes the TextView at least this many lines tall.
2916 *
2917 * Setting this value overrides any other (minimum) height setting. A single line TextView will
2918 * set this value to 1.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002919 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002920 * @see #getMinLines()
2921 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002922 * @attr ref android.R.styleable#TextView_minLines
2923 */
2924 @android.view.RemotableViewMethod
2925 public void setMinLines(int minlines) {
2926 mMinimum = minlines;
2927 mMinMode = LINES;
2928
2929 requestLayout();
2930 invalidate();
2931 }
2932
2933 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002934 * @return the minimum number of lines displayed in this TextView, or -1 if the minimum
2935 * height was set in pixels instead using {@link #setMinHeight(int) or #setHeight(int)}.
2936 *
2937 * @see #setMinLines(int)
2938 *
2939 * @attr ref android.R.styleable#TextView_minLines
2940 */
2941 public int getMinLines() {
2942 return mMinMode == LINES ? mMinimum : -1;
2943 }
2944
2945 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08002946 * Makes the TextView at least this many pixels tall.
2947 *
2948 * Setting this value overrides any other (minimum) number of lines setting.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002949 *
2950 * @attr ref android.R.styleable#TextView_minHeight
2951 */
2952 @android.view.RemotableViewMethod
2953 public void setMinHeight(int minHeight) {
2954 mMinimum = minHeight;
2955 mMinMode = PIXELS;
2956
2957 requestLayout();
2958 invalidate();
2959 }
2960
2961 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002962 * @return the minimum height of this TextView expressed in pixels, or -1 if the minimum
2963 * height was set in number of lines instead using {@link #setMinLines(int) or #setLines(int)}.
2964 *
2965 * @see #setMinHeight(int)
2966 *
2967 * @attr ref android.R.styleable#TextView_minHeight
2968 */
2969 public int getMinHeight() {
2970 return mMinMode == PIXELS ? mMinimum : -1;
2971 }
2972
2973 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08002974 * Makes the TextView at most this many lines tall.
2975 *
2976 * Setting this value overrides any other (maximum) height setting.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002977 *
2978 * @attr ref android.R.styleable#TextView_maxLines
2979 */
2980 @android.view.RemotableViewMethod
2981 public void setMaxLines(int maxlines) {
2982 mMaximum = maxlines;
2983 mMaxMode = LINES;
2984
2985 requestLayout();
2986 invalidate();
2987 }
2988
2989 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002990 * @return the maximum number of lines displayed in this TextView, or -1 if the maximum
2991 * height was set in pixels instead using {@link #setMaxHeight(int) or #setHeight(int)}.
2992 *
2993 * @see #setMaxLines(int)
2994 *
2995 * @attr ref android.R.styleable#TextView_maxLines
2996 */
2997 public int getMaxLines() {
2998 return mMaxMode == LINES ? mMaximum : -1;
2999 }
3000
3001 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003002 * Makes the TextView at most this many pixels tall. This option is mutually exclusive with the
3003 * {@link #setMaxLines(int)} method.
3004 *
3005 * Setting this value overrides any other (maximum) number of lines setting.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003006 *
3007 * @attr ref android.R.styleable#TextView_maxHeight
3008 */
3009 @android.view.RemotableViewMethod
3010 public void setMaxHeight(int maxHeight) {
3011 mMaximum = maxHeight;
3012 mMaxMode = PIXELS;
3013
3014 requestLayout();
3015 invalidate();
3016 }
3017
3018 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003019 * @return the maximum height of this TextView expressed in pixels, or -1 if the maximum
3020 * height was set in number of lines instead using {@link #setMaxLines(int) or #setLines(int)}.
3021 *
3022 * @see #setMaxHeight(int)
3023 *
3024 * @attr ref android.R.styleable#TextView_maxHeight
3025 */
3026 public int getMaxHeight() {
3027 return mMaxMode == PIXELS ? mMaximum : -1;
3028 }
3029
3030 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003031 * Makes the TextView exactly this many lines tall.
3032 *
3033 * Note that setting this value overrides any other (minimum / maximum) number of lines or
3034 * height setting. A single line TextView will set this value to 1.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003035 *
3036 * @attr ref android.R.styleable#TextView_lines
3037 */
3038 @android.view.RemotableViewMethod
3039 public void setLines(int lines) {
3040 mMaximum = mMinimum = lines;
3041 mMaxMode = mMinMode = LINES;
3042
3043 requestLayout();
3044 invalidate();
3045 }
3046
3047 /**
3048 * Makes the TextView exactly this many pixels tall.
3049 * You could do the same thing by specifying this number in the
3050 * LayoutParams.
3051 *
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003052 * Note that setting this value overrides any other (minimum / maximum) number of lines or
3053 * height setting.
3054 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003055 * @attr ref android.R.styleable#TextView_height
3056 */
3057 @android.view.RemotableViewMethod
3058 public void setHeight(int pixels) {
3059 mMaximum = mMinimum = pixels;
3060 mMaxMode = mMinMode = PIXELS;
3061
3062 requestLayout();
3063 invalidate();
3064 }
3065
3066 /**
3067 * Makes the TextView at least this many ems wide
3068 *
3069 * @attr ref android.R.styleable#TextView_minEms
3070 */
3071 @android.view.RemotableViewMethod
3072 public void setMinEms(int minems) {
3073 mMinWidth = minems;
3074 mMinWidthMode = EMS;
3075
3076 requestLayout();
3077 invalidate();
3078 }
3079
3080 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003081 * @return the minimum width of the TextView, expressed in ems or -1 if the minimum width
3082 * was set in pixels instead (using {@link #setMinWidth(int)} or {@link #setWidth(int)}).
3083 *
3084 * @see #setMinEms(int)
3085 * @see #setEms(int)
3086 *
3087 * @attr ref android.R.styleable#TextView_minEms
3088 */
3089 public int getMinEms() {
3090 return mMinWidthMode == EMS ? mMinWidth : -1;
3091 }
3092
3093 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003094 * Makes the TextView at least this many pixels wide
3095 *
3096 * @attr ref android.R.styleable#TextView_minWidth
3097 */
3098 @android.view.RemotableViewMethod
3099 public void setMinWidth(int minpixels) {
3100 mMinWidth = minpixels;
3101 mMinWidthMode = PIXELS;
3102
3103 requestLayout();
3104 invalidate();
3105 }
3106
3107 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003108 * @return the minimum width of the TextView, in pixels or -1 if the minimum width
3109 * was set in ems instead (using {@link #setMinEms(int)} or {@link #setEms(int)}).
3110 *
3111 * @see #setMinWidth(int)
3112 * @see #setWidth(int)
3113 *
3114 * @attr ref android.R.styleable#TextView_minWidth
3115 */
3116 public int getMinWidth() {
3117 return mMinWidthMode == PIXELS ? mMinWidth : -1;
3118 }
3119
3120 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003121 * Makes the TextView at most this many ems wide
3122 *
3123 * @attr ref android.R.styleable#TextView_maxEms
3124 */
3125 @android.view.RemotableViewMethod
3126 public void setMaxEms(int maxems) {
3127 mMaxWidth = maxems;
3128 mMaxWidthMode = EMS;
3129
3130 requestLayout();
3131 invalidate();
3132 }
3133
3134 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003135 * @return the maximum width of the TextView, expressed in ems or -1 if the maximum width
3136 * was set in pixels instead (using {@link #setMaxWidth(int)} or {@link #setWidth(int)}).
3137 *
3138 * @see #setMaxEms(int)
3139 * @see #setEms(int)
3140 *
3141 * @attr ref android.R.styleable#TextView_maxEms
3142 */
3143 public int getMaxEms() {
3144 return mMaxWidthMode == EMS ? mMaxWidth : -1;
3145 }
3146
3147 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003148 * Makes the TextView at most this many pixels wide
3149 *
3150 * @attr ref android.R.styleable#TextView_maxWidth
3151 */
3152 @android.view.RemotableViewMethod
3153 public void setMaxWidth(int maxpixels) {
3154 mMaxWidth = maxpixels;
3155 mMaxWidthMode = PIXELS;
3156
3157 requestLayout();
3158 invalidate();
3159 }
3160
3161 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003162 * @return the maximum width of the TextView, in pixels or -1 if the maximum width
3163 * was set in ems instead (using {@link #setMaxEms(int)} or {@link #setEms(int)}).
3164 *
3165 * @see #setMaxWidth(int)
3166 * @see #setWidth(int)
3167 *
3168 * @attr ref android.R.styleable#TextView_maxWidth
3169 */
3170 public int getMaxWidth() {
3171 return mMaxWidthMode == PIXELS ? mMaxWidth : -1;
3172 }
3173
3174 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003175 * Makes the TextView exactly this many ems wide
3176 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003177 * @see #setMaxEms(int)
3178 * @see #setMinEms(int)
3179 * @see #getMinEms()
3180 * @see #getMaxEms()
3181 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003182 * @attr ref android.R.styleable#TextView_ems
3183 */
3184 @android.view.RemotableViewMethod
3185 public void setEms(int ems) {
3186 mMaxWidth = mMinWidth = ems;
3187 mMaxWidthMode = mMinWidthMode = EMS;
3188
3189 requestLayout();
3190 invalidate();
3191 }
3192
3193 /**
3194 * Makes the TextView exactly this many pixels wide.
3195 * You could do the same thing by specifying this number in the
3196 * LayoutParams.
3197 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003198 * @see #setMaxWidth(int)
3199 * @see #setMinWidth(int)
3200 * @see #getMinWidth()
3201 * @see #getMaxWidth()
3202 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003203 * @attr ref android.R.styleable#TextView_width
3204 */
3205 @android.view.RemotableViewMethod
3206 public void setWidth(int pixels) {
3207 mMaxWidth = mMinWidth = pixels;
3208 mMaxWidthMode = mMinWidthMode = PIXELS;
3209
3210 requestLayout();
3211 invalidate();
3212 }
3213
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003214 /**
3215 * Sets line spacing for this TextView. Each line will have its height
3216 * multiplied by <code>mult</code> and have <code>add</code> added to it.
3217 *
3218 * @attr ref android.R.styleable#TextView_lineSpacingExtra
3219 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
3220 */
3221 public void setLineSpacing(float add, float mult) {
Gilles Debunne22378292011-08-12 10:38:52 -07003222 if (mSpacingAdd != add || mSpacingMult != mult) {
3223 mSpacingAdd = add;
3224 mSpacingMult = mult;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003225
Gilles Debunne22378292011-08-12 10:38:52 -07003226 if (mLayout != null) {
3227 nullLayouts();
3228 requestLayout();
3229 invalidate();
3230 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003231 }
3232 }
3233
3234 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003235 * Gets the line spacing multiplier
3236 *
3237 * @return the value by which each line's height is multiplied to get its actual height.
3238 *
3239 * @see #setLineSpacing(float, float)
3240 * @see #getLineSpacingExtra()
3241 *
3242 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
3243 */
3244 public float getLineSpacingMultiplier() {
3245 return mSpacingMult;
3246 }
3247
3248 /**
3249 * Gets the line spacing extra space
3250 *
3251 * @return the extra space that is added to the height of each lines of this TextView.
3252 *
3253 * @see #setLineSpacing(float, float)
3254 * @see #getLineSpacingMultiplier()
3255 *
3256 * @attr ref android.R.styleable#TextView_lineSpacingExtra
3257 */
3258 public float getLineSpacingExtra() {
3259 return mSpacingAdd;
3260 }
3261
3262 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003263 * Convenience method: Append the specified text to the TextView's
3264 * display buffer, upgrading it to BufferType.EDITABLE if it was
3265 * not already editable.
3266 */
3267 public final void append(CharSequence text) {
3268 append(text, 0, text.length());
3269 }
3270
3271 /**
3272 * Convenience method: Append the specified text slice to the TextView's
3273 * display buffer, upgrading it to BufferType.EDITABLE if it was
3274 * not already editable.
3275 */
3276 public void append(CharSequence text, int start, int end) {
3277 if (!(mText instanceof Editable)) {
3278 setText(mText, BufferType.EDITABLE);
3279 }
3280
3281 ((Editable) mText).append(text, start, end);
3282 }
3283
3284 private void updateTextColors() {
3285 boolean inval = false;
3286 int color = mTextColor.getColorForState(getDrawableState(), 0);
3287 if (color != mCurTextColor) {
3288 mCurTextColor = color;
3289 inval = true;
3290 }
3291 if (mLinkTextColor != null) {
3292 color = mLinkTextColor.getColorForState(getDrawableState(), 0);
3293 if (color != mTextPaint.linkColor) {
3294 mTextPaint.linkColor = color;
3295 inval = true;
3296 }
3297 }
3298 if (mHintTextColor != null) {
3299 color = mHintTextColor.getColorForState(getDrawableState(), 0);
3300 if (color != mCurHintTextColor && mText.length() == 0) {
3301 mCurHintTextColor = color;
3302 inval = true;
3303 }
3304 }
3305 if (inval) {
Gilles Debunne33b7de852012-03-12 11:57:48 -07003306 // Text needs to be redrawn with the new color
Gilles Debunne2d373a12012-04-20 15:32:19 -07003307 if (mEditor != null) mEditor.invalidateTextDisplayList();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003308 invalidate();
3309 }
3310 }
3311
3312 @Override
3313 protected void drawableStateChanged() {
3314 super.drawableStateChanged();
3315 if (mTextColor != null && mTextColor.isStateful()
3316 || (mHintTextColor != null && mHintTextColor.isStateful())
3317 || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
3318 updateTextColors();
3319 }
3320
3321 final Drawables dr = mDrawables;
3322 if (dr != null) {
3323 int[] state = getDrawableState();
3324 if (dr.mDrawableTop != null && dr.mDrawableTop.isStateful()) {
3325 dr.mDrawableTop.setState(state);
3326 }
3327 if (dr.mDrawableBottom != null && dr.mDrawableBottom.isStateful()) {
3328 dr.mDrawableBottom.setState(state);
3329 }
3330 if (dr.mDrawableLeft != null && dr.mDrawableLeft.isStateful()) {
3331 dr.mDrawableLeft.setState(state);
3332 }
3333 if (dr.mDrawableRight != null && dr.mDrawableRight.isStateful()) {
3334 dr.mDrawableRight.setState(state);
3335 }
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07003336 if (dr.mDrawableStart != null && dr.mDrawableStart.isStateful()) {
3337 dr.mDrawableStart.setState(state);
3338 }
3339 if (dr.mDrawableEnd != null && dr.mDrawableEnd.isStateful()) {
3340 dr.mDrawableEnd.setState(state);
3341 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003342 }
3343 }
3344
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003345 @Override
3346 public Parcelable onSaveInstanceState() {
3347 Parcelable superState = super.onSaveInstanceState();
3348
3349 // Save state if we are forced to
3350 boolean save = mFreezesText;
3351 int start = 0;
3352 int end = 0;
3353
3354 if (mText != null) {
Gilles Debunne05336272010-07-09 20:13:45 -07003355 start = getSelectionStart();
3356 end = getSelectionEnd();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003357 if (start >= 0 || end >= 0) {
3358 // Or save state if there is a selection
3359 save = true;
3360 }
3361 }
3362
3363 if (save) {
3364 SavedState ss = new SavedState(superState);
3365 // XXX Should also save the current scroll position!
3366 ss.selStart = start;
3367 ss.selEnd = end;
3368
3369 if (mText instanceof Spanned) {
3370 /*
3371 * Calling setText() strips off any ChangeWatchers;
3372 * strip them now to avoid leaking references.
3373 * But do it to a copy so that if there are any
3374 * further changes to the text of this view, it
3375 * won't get into an inconsistent state.
3376 */
3377
3378 Spannable sp = new SpannableString(mText);
3379
Gilles Debunne176cd0d2011-09-29 16:37:27 -07003380 for (ChangeWatcher cw : sp.getSpans(0, sp.length(), ChangeWatcher.class)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003381 sp.removeSpan(cw);
3382 }
3383
Gilles Debunne60e21862012-01-30 15:04:14 -08003384 if (mEditor != null) {
3385 removeMisspelledSpans(sp);
Gilles Debunne2d373a12012-04-20 15:32:19 -07003386 sp.removeSpan(mEditor.mSuggestionRangeSpan);
Gilles Debunne60e21862012-01-30 15:04:14 -08003387 }
Gilles Debunneaa67eef2011-06-01 18:03:37 -07003388
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003389 ss.text = sp;
3390 } else {
3391 ss.text = mText.toString();
3392 }
3393
3394 if (isFocused() && start >= 0 && end >= 0) {
3395 ss.frozenWithFocus = true;
3396 }
3397
Gilles Debunne60e21862012-01-30 15:04:14 -08003398 ss.error = getError();
The Android Open Source Project4df24232009-03-05 14:34:35 -08003399
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003400 return ss;
3401 }
3402
3403 return superState;
3404 }
3405
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07003406 void removeMisspelledSpans(Spannable spannable) {
3407 SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(),
3408 SuggestionSpan.class);
3409 for (int i = 0; i < suggestionSpans.length; i++) {
3410 int flags = suggestionSpans[i].getFlags();
3411 if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
3412 && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
3413 spannable.removeSpan(suggestionSpans[i]);
3414 }
3415 }
3416 }
3417
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003418 @Override
3419 public void onRestoreInstanceState(Parcelable state) {
3420 if (!(state instanceof SavedState)) {
3421 super.onRestoreInstanceState(state);
3422 return;
3423 }
3424
3425 SavedState ss = (SavedState)state;
3426 super.onRestoreInstanceState(ss.getSuperState());
3427
3428 // XXX restore buffer type too, as well as lots of other stuff
3429 if (ss.text != null) {
3430 setText(ss.text);
3431 }
3432
3433 if (ss.selStart >= 0 && ss.selEnd >= 0) {
3434 if (mText instanceof Spannable) {
3435 int len = mText.length();
3436
3437 if (ss.selStart > len || ss.selEnd > len) {
3438 String restored = "";
3439
3440 if (ss.text != null) {
3441 restored = "(restored) ";
3442 }
3443
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07003444 Log.e(LOG_TAG, "Saved cursor position " + ss.selStart +
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003445 "/" + ss.selEnd + " out of range for " + restored +
3446 "text " + mText);
3447 } else {
Gilles Debunnec1e79b42012-02-24 17:29:31 -08003448 Selection.setSelection((Spannable) mText, ss.selStart, ss.selEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003449
3450 if (ss.frozenWithFocus) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07003451 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07003452 mEditor.mFrozenWithFocus = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003453 }
3454 }
3455 }
3456 }
The Android Open Source Project4df24232009-03-05 14:34:35 -08003457
3458 if (ss.error != null) {
Romain Guy9bc9fa12009-07-21 16:57:29 -07003459 final CharSequence error = ss.error;
3460 // Display the error later, after the first layout pass
3461 post(new Runnable() {
3462 public void run() {
3463 setError(error);
3464 }
3465 });
The Android Open Source Project4df24232009-03-05 14:34:35 -08003466 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003467 }
3468
3469 /**
3470 * Control whether this text view saves its entire text contents when
3471 * freezing to an icicle, in addition to dynamic state such as cursor
3472 * position. By default this is false, not saving the text. Set to true
3473 * if the text in the text view is not being saved somewhere else in
3474 * persistent storage (such as in a content provider) so that if the
3475 * view is later thawed the user will not lose their data.
3476 *
3477 * @param freezesText Controls whether a frozen icicle should include the
3478 * entire text data: true to include it, false to not.
3479 *
3480 * @attr ref android.R.styleable#TextView_freezesText
3481 */
3482 @android.view.RemotableViewMethod
3483 public void setFreezesText(boolean freezesText) {
3484 mFreezesText = freezesText;
3485 }
3486
3487 /**
3488 * Return whether this text view is including its entire text contents
3489 * in frozen icicles.
3490 *
3491 * @return Returns true if text is included, false if it isn't.
3492 *
3493 * @see #setFreezesText
3494 */
3495 public boolean getFreezesText() {
3496 return mFreezesText;
3497 }
3498
3499 ///////////////////////////////////////////////////////////////////////////
3500
3501 /**
3502 * Sets the Factory used to create new Editables.
3503 */
3504 public final void setEditableFactory(Editable.Factory factory) {
3505 mEditableFactory = factory;
3506 setText(mText);
3507 }
3508
3509 /**
3510 * Sets the Factory used to create new Spannables.
3511 */
3512 public final void setSpannableFactory(Spannable.Factory factory) {
3513 mSpannableFactory = factory;
3514 setText(mText);
3515 }
3516
3517 /**
3518 * Sets the string value of the TextView. TextView <em>does not</em> accept
3519 * HTML-like formatting, which you can do with text strings in XML resource files.
3520 * To style your strings, attach android.text.style.* objects to a
3521 * {@link android.text.SpannableString SpannableString}, or see the
3522 * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
Gilles Debunne21078e42011-08-02 10:22:35 -07003523 * Available Resource Types</a> documentation for an example of setting
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003524 * formatted text in the XML resource file.
3525 *
3526 * @attr ref android.R.styleable#TextView_text
3527 */
3528 @android.view.RemotableViewMethod
3529 public final void setText(CharSequence text) {
3530 setText(text, mBufferType);
3531 }
3532
3533 /**
3534 * Like {@link #setText(CharSequence)},
3535 * except that the cursor position (if any) is retained in the new text.
3536 *
3537 * @param text The new text to place in the text view.
3538 *
3539 * @see #setText(CharSequence)
3540 */
3541 @android.view.RemotableViewMethod
3542 public final void setTextKeepState(CharSequence text) {
3543 setTextKeepState(text, mBufferType);
3544 }
3545
3546 /**
3547 * Sets the text that this TextView is to display (see
3548 * {@link #setText(CharSequence)}) and also sets whether it is stored
3549 * in a styleable/spannable buffer and whether it is editable.
3550 *
3551 * @attr ref android.R.styleable#TextView_text
3552 * @attr ref android.R.styleable#TextView_bufferType
3553 */
3554 public void setText(CharSequence text, BufferType type) {
3555 setText(text, type, true, 0);
3556
3557 if (mCharWrapper != null) {
3558 mCharWrapper.mChars = null;
3559 }
3560 }
3561
3562 private void setText(CharSequence text, BufferType type,
3563 boolean notifyBefore, int oldlen) {
3564 if (text == null) {
3565 text = "";
3566 }
3567
Luca Zanoline0760452011-09-08 12:03:37 +01003568 // If suggestions are not enabled, remove the suggestion spans from the text
3569 if (!isSuggestionsEnabled()) {
3570 text = removeSuggestionSpans(text);
3571 }
3572
Romain Guy939151f2009-04-08 14:22:40 -07003573 if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
3574
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003575 if (text instanceof Spanned &&
3576 ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
Adam Powell282e3772011-08-30 16:51:11 -07003577 if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {
3578 setHorizontalFadingEdgeEnabled(true);
3579 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
3580 } else {
3581 setHorizontalFadingEdgeEnabled(false);
3582 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
3583 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003584 setEllipsize(TextUtils.TruncateAt.MARQUEE);
3585 }
3586
3587 int n = mFilters.length;
3588 for (int i = 0; i < n; i++) {
Gilles Debunnec1714022012-01-17 13:59:23 -08003589 CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003590 if (out != null) {
3591 text = out;
3592 }
3593 }
3594
3595 if (notifyBefore) {
3596 if (mText != null) {
3597 oldlen = mText.length();
3598 sendBeforeTextChanged(mText, 0, oldlen, text.length());
3599 } else {
3600 sendBeforeTextChanged("", 0, 0, text.length());
3601 }
3602 }
3603
3604 boolean needEditableForNotification = false;
3605
3606 if (mListeners != null && mListeners.size() != 0) {
3607 needEditableForNotification = true;
3608 }
3609
Gilles Debunne2d373a12012-04-20 15:32:19 -07003610 if (type == BufferType.EDITABLE || getKeyListener() != null ||
3611 needEditableForNotification) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07003612 createEditorIfNeeded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003613 Editable t = mEditableFactory.newEditable(text);
3614 text = t;
3615 setFilters(t, mFilters);
3616 InputMethodManager imm = InputMethodManager.peekInstance();
3617 if (imm != null) imm.restartInput(this);
3618 } else if (type == BufferType.SPANNABLE || mMovement != null) {
3619 text = mSpannableFactory.newSpannable(text);
3620 } else if (!(text instanceof CharWrapper)) {
3621 text = TextUtils.stringOrSpannedString(text);
3622 }
3623
3624 if (mAutoLinkMask != 0) {
3625 Spannable s2;
3626
3627 if (type == BufferType.EDITABLE || text instanceof Spannable) {
3628 s2 = (Spannable) text;
3629 } else {
3630 s2 = mSpannableFactory.newSpannable(text);
3631 }
3632
3633 if (Linkify.addLinks(s2, mAutoLinkMask)) {
3634 text = s2;
3635 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
3636
3637 /*
3638 * We must go ahead and set the text before changing the
3639 * movement method, because setMovementMethod() may call
3640 * setText() again to try to upgrade the buffer type.
3641 */
3642 mText = text;
3643
Gilles Debunnecbcb3452010-12-17 15:31:02 -08003644 // Do not change the movement method for text that support text selection as it
3645 // would prevent an arbitrary cursor displacement.
Gilles Debunnebb588da2011-07-11 18:26:19 -07003646 if (mLinksClickable && !textCanBeSelected()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003647 setMovementMethod(LinkMovementMethod.getInstance());
3648 }
3649 }
3650 }
3651
3652 mBufferType = type;
3653 mText = text;
3654
Adam Powell7f8f79a2011-07-07 18:35:54 -07003655 if (mTransformation == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003656 mTransformed = text;
Adam Powell7f8f79a2011-07-07 18:35:54 -07003657 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003658 mTransformed = mTransformation.getTransformation(text, this);
Adam Powell7f8f79a2011-07-07 18:35:54 -07003659 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003660
3661 final int textLength = text.length();
3662
Adam Powell7f8f79a2011-07-07 18:35:54 -07003663 if (text instanceof Spannable && !mAllowTransformationLengthChange) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003664 Spannable sp = (Spannable) text;
3665
Gilles Debunnec62589c2012-04-12 14:50:23 -07003666 // Remove any ChangeWatchers that might have come from other TextViews.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003667 final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
3668 final int count = watchers.length;
Gilles Debunnec62589c2012-04-12 14:50:23 -07003669 for (int i = 0; i < count; i++) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003670 sp.removeSpan(watchers[i]);
Gilles Debunnec62589c2012-04-12 14:50:23 -07003671 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003672
Gilles Debunnec62589c2012-04-12 14:50:23 -07003673 if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003674
3675 sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE |
Gilles Debunne60e21862012-01-30 15:04:14 -08003676 (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003677
Gilles Debunnec62589c2012-04-12 14:50:23 -07003678 if (mEditor != null) mEditor.addSpanWatchers(sp);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003679
3680 if (mTransformation != null) {
3681 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003682 }
3683
3684 if (mMovement != null) {
3685 mMovement.initialize(this, (Spannable) text);
3686
3687 /*
3688 * Initializing the movement method will have set the
3689 * selection, so reset mSelectionMoved to keep that from
3690 * interfering with the normal on-focus selection-setting.
3691 */
Gilles Debunne2d373a12012-04-20 15:32:19 -07003692 if (mEditor != null) mEditor.mSelectionMoved = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003693 }
3694 }
3695
3696 if (mLayout != null) {
3697 checkForRelayout();
3698 }
3699
3700 sendOnTextChanged(text, 0, oldlen, textLength);
3701 onTextChanged(text, 0, oldlen, textLength);
3702
3703 if (needEditableForNotification) {
3704 sendAfterTextChanged((Editable) text);
3705 }
Gilles Debunne05336272010-07-09 20:13:45 -07003706
Gilles Debunnebaaace52010-10-01 15:47:13 -07003707 // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
Gilles Debunne2d373a12012-04-20 15:32:19 -07003708 if (mEditor != null) mEditor.prepareCursorControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003709 }
3710
3711 /**
3712 * Sets the TextView to display the specified slice of the specified
3713 * char array. You must promise that you will not change the contents
3714 * of the array except for right before another call to setText(),
3715 * since the TextView has no way to know that the text
3716 * has changed and that it needs to invalidate and re-layout.
3717 */
3718 public final void setText(char[] text, int start, int len) {
3719 int oldlen = 0;
3720
3721 if (start < 0 || len < 0 || start + len > text.length) {
3722 throw new IndexOutOfBoundsException(start + ", " + len);
3723 }
3724
3725 /*
3726 * We must do the before-notification here ourselves because if
3727 * the old text is a CharWrapper we destroy it before calling
3728 * into the normal path.
3729 */
3730 if (mText != null) {
3731 oldlen = mText.length();
3732 sendBeforeTextChanged(mText, 0, oldlen, len);
3733 } else {
3734 sendBeforeTextChanged("", 0, 0, len);
3735 }
3736
3737 if (mCharWrapper == null) {
3738 mCharWrapper = new CharWrapper(text, start, len);
3739 } else {
3740 mCharWrapper.set(text, start, len);
3741 }
3742
3743 setText(mCharWrapper, mBufferType, false, oldlen);
3744 }
3745
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003746 /**
3747 * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)},
3748 * except that the cursor position (if any) is retained in the new text.
3749 *
3750 * @see #setText(CharSequence, android.widget.TextView.BufferType)
3751 */
3752 public final void setTextKeepState(CharSequence text, BufferType type) {
3753 int start = getSelectionStart();
3754 int end = getSelectionEnd();
3755 int len = text.length();
3756
3757 setText(text, type);
3758
3759 if (start >= 0 || end >= 0) {
3760 if (mText instanceof Spannable) {
3761 Selection.setSelection((Spannable) mText,
3762 Math.max(0, Math.min(start, len)),
3763 Math.max(0, Math.min(end, len)));
3764 }
3765 }
3766 }
3767
3768 @android.view.RemotableViewMethod
3769 public final void setText(int resid) {
3770 setText(getContext().getResources().getText(resid));
3771 }
3772
3773 public final void setText(int resid, BufferType type) {
3774 setText(getContext().getResources().getText(resid), type);
3775 }
3776
3777 /**
3778 * Sets the text to be displayed when the text of the TextView is empty.
3779 * Null means to use the normal empty text. The hint does not currently
3780 * participate in determining the size of the view.
3781 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003782 * @attr ref android.R.styleable#TextView_hint
3783 */
3784 @android.view.RemotableViewMethod
3785 public final void setHint(CharSequence hint) {
3786 mHint = TextUtils.stringOrSpannedString(hint);
3787
3788 if (mLayout != null) {
3789 checkForRelayout();
3790 }
3791
Romain Guy4dc4f732009-06-19 15:16:40 -07003792 if (mText.length() == 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003793 invalidate();
Romain Guy4dc4f732009-06-19 15:16:40 -07003794 }
Gilles Debunne626c3162012-02-14 15:46:41 -08003795
Gilles Debunne33b7de852012-03-12 11:57:48 -07003796 // Invalidate display list if hint is currently used
Gilles Debunne60e21862012-01-30 15:04:14 -08003797 if (mEditor != null && mText.length() == 0 && mHint != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07003798 mEditor.invalidateTextDisplayList();
Gilles Debunne60e21862012-01-30 15:04:14 -08003799 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003800 }
3801
3802 /**
3803 * Sets the text to be displayed when the text of the TextView is empty,
3804 * from a resource.
3805 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003806 * @attr ref android.R.styleable#TextView_hint
3807 */
3808 @android.view.RemotableViewMethod
3809 public final void setHint(int resid) {
3810 setHint(getContext().getResources().getText(resid));
3811 }
3812
3813 /**
3814 * Returns the hint that is displayed when the text of the TextView
3815 * is empty.
3816 *
3817 * @attr ref android.R.styleable#TextView_hint
3818 */
3819 @ViewDebug.CapturedViewProperty
3820 public CharSequence getHint() {
3821 return mHint;
3822 }
3823
Gilles Debunned88876a2012-03-16 17:34:04 -07003824 boolean isSingleLine() {
3825 return mSingleLine;
3826 }
3827
Gilles Debunne3784a7f2011-07-15 13:49:38 -07003828 private static boolean isMultilineInputType(int type) {
Gilles Debunne91a08cf2010-11-08 17:34:49 -08003829 return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) ==
3830 (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
3831 }
3832
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003833 /**
Gilles Debunned88876a2012-03-16 17:34:04 -07003834 * Removes the suggestion spans.
3835 */
3836 CharSequence removeSuggestionSpans(CharSequence text) {
3837 if (text instanceof Spanned) {
3838 Spannable spannable;
3839 if (text instanceof Spannable) {
3840 spannable = (Spannable) text;
3841 } else {
3842 spannable = new SpannableString(text);
3843 text = spannable;
3844 }
3845
3846 SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
3847 for (int i = 0; i < spans.length; i++) {
3848 spannable.removeSpan(spans[i]);
3849 }
3850 }
3851 return text;
3852 }
3853
3854 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003855 * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This
3856 * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)},
3857 * to match the given content type. If the given content type is {@link EditorInfo#TYPE_NULL}
3858 * then a soft keyboard will not be displayed for this text view.
3859 *
3860 * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be
3861 * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input
3862 * type.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003863 *
3864 * @see #getInputType()
3865 * @see #setRawInputType(int)
3866 * @see android.text.InputType
3867 * @attr ref android.R.styleable#TextView_inputType
3868 */
3869 public void setInputType(int type) {
Gilles Debunne60e21862012-01-30 15:04:14 -08003870 final boolean wasPassword = isPasswordInputType(getInputType());
3871 final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003872 setInputType(type, false);
Bjorn Bringertad8da912009-09-17 10:47:35 +01003873 final boolean isPassword = isPasswordInputType(type);
3874 final boolean isVisiblePassword = isVisiblePasswordInputType(type);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003875 boolean forceUpdate = false;
3876 if (isPassword) {
3877 setTransformationMethod(PasswordTransformationMethod.getInstance());
Raph Leviend570e892012-05-09 11:45:34 -07003878 setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
Bjorn Bringertad8da912009-09-17 10:47:35 +01003879 } else if (isVisiblePassword) {
Amith Yamasania8c0edb2009-09-27 16:51:21 -07003880 if (mTransformation == PasswordTransformationMethod.getInstance()) {
3881 forceUpdate = true;
3882 }
Raph Leviend570e892012-05-09 11:45:34 -07003883 setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
Bjorn Bringertad8da912009-09-17 10:47:35 +01003884 } else if (wasPassword || wasVisiblePassword) {
3885 // not in password mode, clean up typeface and transformation
Raph Leviend570e892012-05-09 11:45:34 -07003886 setTypefaceFromAttrs(null /* fontFamily */, -1, -1);
Bjorn Bringertad8da912009-09-17 10:47:35 +01003887 if (mTransformation == PasswordTransformationMethod.getInstance()) {
3888 forceUpdate = true;
3889 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003890 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07003891
Gilles Debunne91a08cf2010-11-08 17:34:49 -08003892 boolean singleLine = !isMultilineInputType(type);
Gilles Debunne2d373a12012-04-20 15:32:19 -07003893
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003894 // We need to update the single line mode if it has changed or we
3895 // were previously in password mode.
Gilles Debunne91a08cf2010-11-08 17:34:49 -08003896 if (mSingleLine != singleLine || forceUpdate) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003897 // Change single line mode, but only change the transformation if
3898 // we are not in password mode.
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003899 applySingleLine(singleLine, !isPassword, true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003900 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07003901
Luca Zanoline0760452011-09-08 12:03:37 +01003902 if (!isSuggestionsEnabled()) {
3903 mText = removeSuggestionSpans(mText);
3904 }
3905
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003906 InputMethodManager imm = InputMethodManager.peekInstance();
3907 if (imm != null) imm.restartInput(this);
3908 }
3909
Gilles Debunne0dcad2b2010-10-15 16:29:25 -07003910 /**
3911 * It would be better to rely on the input type for everything. A password inputType should have
3912 * a password transformation. We should hence use isPasswordInputType instead of this method.
3913 *
3914 * We should:
3915 * - Call setInputType in setKeyListener instead of changing the input type directly (which
3916 * would install the correct transformation).
3917 * - Refuse the installation of a non-password transformation in setTransformation if the input
3918 * type is password.
3919 *
3920 * However, this is like this for legacy reasons and we cannot break existing apps. This method
3921 * is useful since it matches what the user can see (obfuscated text or not).
3922 *
3923 * @return true if the current transformation method is of the password type.
3924 */
3925 private boolean hasPasswordTransformationMethod() {
3926 return mTransformation instanceof PasswordTransformationMethod;
3927 }
3928
Gilles Debunne3784a7f2011-07-15 13:49:38 -07003929 private static boolean isPasswordInputType(int inputType) {
Gilles Debunned7483bf2010-11-10 10:47:45 -08003930 final int variation =
3931 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
Bjorn Bringertad8da912009-09-17 10:47:35 +01003932 return variation
Gilles Debunned7483bf2010-11-10 10:47:45 -08003933 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)
3934 || variation
Ken Wakasa82d731a2010-12-24 23:42:41 +09003935 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD)
3936 || variation
3937 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
Bjorn Bringertad8da912009-09-17 10:47:35 +01003938 }
3939
Gilles Debunne3784a7f2011-07-15 13:49:38 -07003940 private static boolean isVisiblePasswordInputType(int inputType) {
Gilles Debunned7483bf2010-11-10 10:47:45 -08003941 final int variation =
3942 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
Bjorn Bringertad8da912009-09-17 10:47:35 +01003943 return variation
Gilles Debunned7483bf2010-11-10 10:47:45 -08003944 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
Bjorn Bringertad8da912009-09-17 10:47:35 +01003945 }
3946
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003947 /**
3948 * Directly change the content type integer of the text view, without
3949 * modifying any other state.
3950 * @see #setInputType(int)
3951 * @see android.text.InputType
3952 * @attr ref android.R.styleable#TextView_inputType
3953 */
3954 public void setRawInputType(int type) {
Gilles Debunne60e21862012-01-30 15:04:14 -08003955 if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
Gilles Debunne5fae9962012-05-08 14:53:20 -07003956 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07003957 mEditor.mInputType = type;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003958 }
3959
3960 private void setInputType(int type, boolean direct) {
3961 final int cls = type & EditorInfo.TYPE_MASK_CLASS;
3962 KeyListener input;
3963 if (cls == EditorInfo.TYPE_CLASS_TEXT) {
Gilles Debunnee67b58a2010-08-31 15:55:31 -07003964 boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003965 TextKeyListener.Capitalize cap;
3966 if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
3967 cap = TextKeyListener.Capitalize.CHARACTERS;
3968 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
3969 cap = TextKeyListener.Capitalize.WORDS;
3970 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
3971 cap = TextKeyListener.Capitalize.SENTENCES;
3972 } else {
3973 cap = TextKeyListener.Capitalize.NONE;
3974 }
3975 input = TextKeyListener.getInstance(autotext, cap);
3976 } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
3977 input = DigitsKeyListener.getInstance(
3978 (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
3979 (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
3980 } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
3981 switch (type & EditorInfo.TYPE_MASK_VARIATION) {
3982 case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
3983 input = DateKeyListener.getInstance();
3984 break;
3985 case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
3986 input = TimeKeyListener.getInstance();
3987 break;
3988 default:
3989 input = DateTimeKeyListener.getInstance();
3990 break;
3991 }
3992 } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
3993 input = DialerKeyListener.getInstance();
3994 } else {
3995 input = TextKeyListener.getInstance();
3996 }
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07003997 setRawInputType(type);
Gilles Debunne60e21862012-01-30 15:04:14 -08003998 if (direct) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07003999 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004000 mEditor.mKeyListener = input;
Gilles Debunne60e21862012-01-30 15:04:14 -08004001 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004002 setKeyListenerOnly(input);
4003 }
4004 }
4005
4006 /**
Gilles Debunne60e21862012-01-30 15:04:14 -08004007 * Get the type of the editable content.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004008 *
4009 * @see #setInputType(int)
4010 * @see android.text.InputType
4011 */
4012 public int getInputType() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004013 return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004014 }
4015
4016 /**
4017 * Change the editor type integer associated with the text view, which
4018 * will be reported to an IME with {@link EditorInfo#imeOptions} when it
4019 * has focus.
4020 * @see #getImeOptions
4021 * @see android.view.inputmethod.EditorInfo
4022 * @attr ref android.R.styleable#TextView_imeOptions
4023 */
4024 public void setImeOptions(int imeOptions) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004025 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004026 mEditor.createInputContentTypeIfNeeded();
4027 mEditor.mInputContentType.imeOptions = imeOptions;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004028 }
4029
4030 /**
4031 * Get the type of the IME editor.
4032 *
4033 * @see #setImeOptions(int)
4034 * @see android.view.inputmethod.EditorInfo
4035 */
4036 public int getImeOptions() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004037 return mEditor != null && mEditor.mInputContentType != null
4038 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004039 }
4040
4041 /**
4042 * Change the custom IME action associated with the text view, which
4043 * will be reported to an IME with {@link EditorInfo#actionLabel}
4044 * and {@link EditorInfo#actionId} when it has focus.
4045 * @see #getImeActionLabel
4046 * @see #getImeActionId
4047 * @see android.view.inputmethod.EditorInfo
4048 * @attr ref android.R.styleable#TextView_imeActionLabel
4049 * @attr ref android.R.styleable#TextView_imeActionId
4050 */
4051 public void setImeActionLabel(CharSequence label, int actionId) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004052 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004053 mEditor.createInputContentTypeIfNeeded();
4054 mEditor.mInputContentType.imeActionLabel = label;
4055 mEditor.mInputContentType.imeActionId = actionId;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004056 }
4057
4058 /**
4059 * Get the IME action label previous set with {@link #setImeActionLabel}.
4060 *
4061 * @see #setImeActionLabel
4062 * @see android.view.inputmethod.EditorInfo
4063 */
4064 public CharSequence getImeActionLabel() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004065 return mEditor != null && mEditor.mInputContentType != null
4066 ? mEditor.mInputContentType.imeActionLabel : null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004067 }
4068
4069 /**
4070 * Get the IME action ID previous set with {@link #setImeActionLabel}.
4071 *
4072 * @see #setImeActionLabel
4073 * @see android.view.inputmethod.EditorInfo
4074 */
4075 public int getImeActionId() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004076 return mEditor != null && mEditor.mInputContentType != null
4077 ? mEditor.mInputContentType.imeActionId : 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004078 }
4079
4080 /**
4081 * Set a special listener to be called when an action is performed
4082 * on the text view. This will be called when the enter key is pressed,
4083 * or when an action supplied to the IME is selected by the user. Setting
4084 * this means that the normal hard key event will not insert a newline
4085 * into the text view, even if it is multi-line; holding down the ALT
4086 * modifier will, however, allow the user to insert a newline character.
4087 */
4088 public void setOnEditorActionListener(OnEditorActionListener l) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004089 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004090 mEditor.createInputContentTypeIfNeeded();
4091 mEditor.mInputContentType.onEditorActionListener = l;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004092 }
Gilles Debunne60e21862012-01-30 15:04:14 -08004093
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004094 /**
4095 * Called when an attached input method calls
4096 * {@link InputConnection#performEditorAction(int)
4097 * InputConnection.performEditorAction()}
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07004098 * for this text view. The default implementation will call your action
4099 * listener supplied to {@link #setOnEditorActionListener}, or perform
4100 * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
Dianne Hackborndea3ef72010-10-28 14:24:22 -07004101 * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS
4102 * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07004103 * EditorInfo.IME_ACTION_DONE}.
Gilles Debunne2d373a12012-04-20 15:32:19 -07004104 *
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07004105 * <p>For backwards compatibility, if no IME options have been set and the
4106 * text view would not normally advance focus on enter, then
4107 * the NEXT and DONE actions received here will be turned into an enter
4108 * key down/up pair to go through the normal key handling.
Gilles Debunne2d373a12012-04-20 15:32:19 -07004109 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004110 * @param actionCode The code of the action being performed.
Gilles Debunne2d373a12012-04-20 15:32:19 -07004111 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004112 * @see #setOnEditorActionListener
4113 */
4114 public void onEditorAction(int actionCode) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004115 final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004116 if (ict != null) {
4117 if (ict.onEditorActionListener != null) {
4118 if (ict.onEditorActionListener.onEditorAction(this,
4119 actionCode, null)) {
4120 return;
4121 }
4122 }
Gilles Debunne64794482011-11-30 15:45:28 -08004123
The Android Open Source Project4df24232009-03-05 14:34:35 -08004124 // This is the handling for some default action.
4125 // Note that for backwards compatibility we don't do this
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004126 // default handling if explicit ime options have not been given,
The Android Open Source Project10592532009-03-18 17:39:46 -07004127 // instead turning this into the normal enter key codes that an
The Android Open Source Project4df24232009-03-05 14:34:35 -08004128 // app may be expecting.
4129 if (actionCode == EditorInfo.IME_ACTION_NEXT) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004130 View v = focusSearch(FOCUS_FORWARD);
The Android Open Source Project4df24232009-03-05 14:34:35 -08004131 if (v != null) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004132 if (!v.requestFocus(FOCUS_FORWARD)) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08004133 throw new IllegalStateException("focus search returned a view " +
4134 "that wasn't able to take focus!");
4135 }
4136 }
4137 return;
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004138
Dianne Hackborndea3ef72010-10-28 14:24:22 -07004139 } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004140 View v = focusSearch(FOCUS_BACKWARD);
Dianne Hackborndea3ef72010-10-28 14:24:22 -07004141 if (v != null) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004142 if (!v.requestFocus(FOCUS_BACKWARD)) {
Dianne Hackborndea3ef72010-10-28 14:24:22 -07004143 throw new IllegalStateException("focus search returned a view " +
4144 "that wasn't able to take focus!");
4145 }
4146 }
4147 return;
4148
The Android Open Source Project4df24232009-03-05 14:34:35 -08004149 } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
4150 InputMethodManager imm = InputMethodManager.peekInstance();
Gilles Debunne17d31de2011-01-27 11:02:18 -08004151 if (imm != null && imm.isActive(this)) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08004152 imm.hideSoftInputFromWindow(getWindowToken(), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004153 }
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07004154 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004155 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004156 }
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004157
Jeff Browna175a5b2012-02-15 19:18:31 -08004158 ViewRootImpl viewRootImpl = getViewRootImpl();
4159 if (viewRootImpl != null) {
The Android Open Source Project10592532009-03-18 17:39:46 -07004160 long eventTime = SystemClock.uptimeMillis();
Jeff Browna175a5b2012-02-15 19:18:31 -08004161 viewRootImpl.dispatchKeyFromIme(
The Android Open Source Project10592532009-03-18 17:39:46 -07004162 new KeyEvent(eventTime, eventTime,
Jeff Brown6b53e8d2010-11-10 16:03:06 -08004163 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
4164 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
The Android Open Source Project10592532009-03-18 17:39:46 -07004165 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
Jeff Browna175a5b2012-02-15 19:18:31 -08004166 | KeyEvent.FLAG_EDITOR_ACTION));
4167 viewRootImpl.dispatchKeyFromIme(
The Android Open Source Project10592532009-03-18 17:39:46 -07004168 new KeyEvent(SystemClock.uptimeMillis(), eventTime,
Jeff Brown6b53e8d2010-11-10 16:03:06 -08004169 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
4170 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
The Android Open Source Project10592532009-03-18 17:39:46 -07004171 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
Jeff Browna175a5b2012-02-15 19:18:31 -08004172 | KeyEvent.FLAG_EDITOR_ACTION));
The Android Open Source Project10592532009-03-18 17:39:46 -07004173 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004174 }
Gilles Debunne64794482011-11-30 15:45:28 -08004175
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004176 /**
4177 * Set the private content type of the text, which is the
4178 * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
4179 * field that will be filled in when creating an input connection.
4180 *
4181 * @see #getPrivateImeOptions()
4182 * @see EditorInfo#privateImeOptions
4183 * @attr ref android.R.styleable#TextView_privateImeOptions
4184 */
4185 public void setPrivateImeOptions(String type) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004186 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004187 mEditor.createInputContentTypeIfNeeded();
4188 mEditor.mInputContentType.privateImeOptions = type;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004189 }
4190
4191 /**
4192 * Get the private type of the content.
4193 *
4194 * @see #setPrivateImeOptions(String)
4195 * @see EditorInfo#privateImeOptions
4196 */
4197 public String getPrivateImeOptions() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004198 return mEditor != null && mEditor.mInputContentType != null
4199 ? mEditor.mInputContentType.privateImeOptions : null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004200 }
4201
4202 /**
4203 * Set the extra input data of the text, which is the
4204 * {@link EditorInfo#extras TextBoxAttribute.extras}
4205 * Bundle that will be filled in when creating an input connection. The
4206 * given integer is the resource ID of an XML resource holding an
4207 * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
4208 *
Gilles Debunne2d373a12012-04-20 15:32:19 -07004209 * @see #getInputExtras(boolean)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004210 * @see EditorInfo#extras
4211 * @attr ref android.R.styleable#TextView_editorExtras
4212 */
Gilles Debunne60e21862012-01-30 15:04:14 -08004213 public void setInputExtras(int xmlResId) throws XmlPullParserException, IOException {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004214 createEditorIfNeeded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004215 XmlResourceParser parser = getResources().getXml(xmlResId);
Gilles Debunne2d373a12012-04-20 15:32:19 -07004216 mEditor.createInputContentTypeIfNeeded();
4217 mEditor.mInputContentType.extras = new Bundle();
4218 getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004219 }
4220
4221 /**
4222 * Retrieve the input extras currently associated with the text view, which
4223 * can be viewed as well as modified.
4224 *
4225 * @param create If true, the extras will be created if they don't already
4226 * exist. Otherwise, null will be returned if none have been created.
Gilles Debunnee15b3582010-06-16 15:17:21 -07004227 * @see #setInputExtras(int)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004228 * @see EditorInfo#extras
4229 * @attr ref android.R.styleable#TextView_editorExtras
4230 */
4231 public Bundle getInputExtras(boolean create) {
Gilles Debunne60e21862012-01-30 15:04:14 -08004232 if (mEditor == null && !create) return null;
Gilles Debunne5fae9962012-05-08 14:53:20 -07004233 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004234 if (mEditor.mInputContentType == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004235 if (!create) return null;
Gilles Debunne2d373a12012-04-20 15:32:19 -07004236 mEditor.createInputContentTypeIfNeeded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004237 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07004238 if (mEditor.mInputContentType.extras == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004239 if (!create) return null;
Gilles Debunne2d373a12012-04-20 15:32:19 -07004240 mEditor.mInputContentType.extras = new Bundle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004241 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07004242 return mEditor.mInputContentType.extras;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004243 }
4244
4245 /**
4246 * Returns the error message that was set to be displayed with
4247 * {@link #setError}, or <code>null</code> if no error was set
4248 * or if it the error was cleared by the widget after user input.
4249 */
4250 public CharSequence getError() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004251 return mEditor == null ? null : mEditor.mError;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004252 }
4253
4254 /**
4255 * Sets the right-hand compound drawable of the TextView to the "error"
4256 * icon and sets an error message that will be displayed in a popup when
4257 * the TextView has focus. The icon and error message will be reset to
4258 * null when any key events cause changes to the TextView's text. If the
4259 * <code>error</code> is <code>null</code>, the error message and icon
4260 * will be cleared.
4261 */
4262 @android.view.RemotableViewMethod
4263 public void setError(CharSequence error) {
4264 if (error == null) {
4265 setError(null, null);
4266 } else {
4267 Drawable dr = getContext().getResources().
Gilles Debunnea85467b2011-01-19 16:53:31 -08004268 getDrawable(com.android.internal.R.drawable.indicator_input_error);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004269
4270 dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
4271 setError(error, dr);
4272 }
4273 }
4274
4275 /**
4276 * Sets the right-hand compound drawable of the TextView to the specified
4277 * icon and sets an error message that will be displayed in a popup when
4278 * the TextView has focus. The icon and error message will be reset to
4279 * null when any key events cause changes to the TextView's text. The
4280 * drawable must already have had {@link Drawable#setBounds} set on it.
4281 * If the <code>error</code> is <code>null</code>, the error message will
4282 * be cleared (and you should provide a <code>null</code> icon as well).
4283 */
4284 public void setError(CharSequence error, Drawable icon) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004285 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004286 mEditor.setError(error, icon);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004287 }
4288
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004289 @Override
4290 protected boolean setFrame(int l, int t, int r, int b) {
4291 boolean result = super.setFrame(l, t, r, b);
4292
Gilles Debunne2d373a12012-04-20 15:32:19 -07004293 if (mEditor != null) mEditor.setFrame();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004294
Romain Guy986003d2009-03-25 17:42:35 -07004295 restartMarqueeIfNeeded();
4296
4297 return result;
4298 }
4299
4300 private void restartMarqueeIfNeeded() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004301 if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
4302 mRestartMarquee = false;
4303 startMarquee();
4304 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004305 }
4306
4307 /**
4308 * Sets the list of input filters that will be used if the buffer is
Gilles Debunne60e21862012-01-30 15:04:14 -08004309 * Editable. Has no effect otherwise.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004310 *
4311 * @attr ref android.R.styleable#TextView_maxLength
4312 */
4313 public void setFilters(InputFilter[] filters) {
4314 if (filters == null) {
4315 throw new IllegalArgumentException();
4316 }
4317
4318 mFilters = filters;
4319
4320 if (mText instanceof Editable) {
4321 setFilters((Editable) mText, filters);
4322 }
4323 }
4324
4325 /**
4326 * Sets the list of input filters on the specified Editable,
4327 * and includes mInput in the list if it is an InputFilter.
4328 */
4329 private void setFilters(Editable e, InputFilter[] filters) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004330 if (mEditor != null && mEditor.mKeyListener instanceof InputFilter) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004331 InputFilter[] nf = new InputFilter[filters.length + 1];
4332
4333 System.arraycopy(filters, 0, nf, 0, filters.length);
Gilles Debunne2d373a12012-04-20 15:32:19 -07004334 nf[filters.length] = (InputFilter) mEditor.mKeyListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004335
4336 e.setFilters(nf);
4337 } else {
4338 e.setFilters(filters);
4339 }
4340 }
4341
4342 /**
4343 * Returns the current list of input filters.
Gilles Debunnef03acef2012-04-30 19:26:19 -07004344 *
4345 * @attr ref android.R.styleable#TextView_maxLength
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004346 */
4347 public InputFilter[] getFilters() {
4348 return mFilters;
4349 }
4350
4351 /////////////////////////////////////////////////////////////////////////
4352
Philip Milne7b757812012-09-19 18:13:44 -07004353 private int getBoxHeight(Layout l) {
4354 Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE;
4355 int padding = (l == mHintLayout) ?
4356 getCompoundPaddingTop() + getCompoundPaddingBottom() :
4357 getExtendedPaddingTop() + getExtendedPaddingBottom();
4358 return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom;
4359 }
4360
Gilles Debunned88876a2012-03-16 17:34:04 -07004361 int getVerticalOffset(boolean forceNormal) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004362 int voffset = 0;
4363 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
4364
4365 Layout l = mLayout;
4366 if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
4367 l = mHintLayout;
4368 }
4369
4370 if (gravity != Gravity.TOP) {
Philip Milne7b757812012-09-19 18:13:44 -07004371 int boxht = getBoxHeight(l);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004372 int textht = l.getHeight();
4373
4374 if (textht < boxht) {
4375 if (gravity == Gravity.BOTTOM)
4376 voffset = boxht - textht;
4377 else // (gravity == Gravity.CENTER_VERTICAL)
4378 voffset = (boxht - textht) >> 1;
4379 }
4380 }
4381 return voffset;
4382 }
4383
4384 private int getBottomVerticalOffset(boolean forceNormal) {
4385 int voffset = 0;
4386 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
4387
4388 Layout l = mLayout;
4389 if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
4390 l = mHintLayout;
4391 }
4392
4393 if (gravity != Gravity.BOTTOM) {
Philip Milne7b757812012-09-19 18:13:44 -07004394 int boxht = getBoxHeight(l);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004395 int textht = l.getHeight();
4396
4397 if (textht < boxht) {
4398 if (gravity == Gravity.TOP)
4399 voffset = boxht - textht;
4400 else // (gravity == Gravity.CENTER_VERTICAL)
4401 voffset = (boxht - textht) >> 1;
4402 }
4403 }
4404 return voffset;
4405 }
4406
Gilles Debunned88876a2012-03-16 17:34:04 -07004407 void invalidateCursorPath() {
Gilles Debunne83051b82012-02-24 20:01:13 -08004408 if (mHighlightPathBogus) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004409 invalidateCursor();
4410 } else {
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004411 final int horizontalPadding = getCompoundPaddingLeft();
4412 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004413
Gilles Debunne2d373a12012-04-20 15:32:19 -07004414 if (mEditor.mCursorCount == 0) {
Gilles Debunne60e21862012-01-30 15:04:14 -08004415 synchronized (TEMP_RECTF) {
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004416 /*
4417 * The reason for this concern about the thickness of the
4418 * cursor and doing the floor/ceil on the coordinates is that
4419 * some EditTexts (notably textfields in the Browser) have
4420 * anti-aliased text where not all the characters are
4421 * necessarily at integer-multiple locations. This should
4422 * make sure the entire cursor gets invalidated instead of
4423 * sometimes missing half a pixel.
4424 */
4425 float thick = FloatMath.ceil(mTextPaint.getStrokeWidth());
4426 if (thick < 1.0f) {
4427 thick = 1.0f;
4428 }
4429
4430 thick /= 2.0f;
4431
Gilles Debunne83051b82012-02-24 20:01:13 -08004432 // mHighlightPath is guaranteed to be non null at that point.
4433 mHighlightPath.computeBounds(TEMP_RECTF, false);
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004434
Gilles Debunne60e21862012-01-30 15:04:14 -08004435 invalidate((int) FloatMath.floor(horizontalPadding + TEMP_RECTF.left - thick),
4436 (int) FloatMath.floor(verticalPadding + TEMP_RECTF.top - thick),
4437 (int) FloatMath.ceil(horizontalPadding + TEMP_RECTF.right + thick),
4438 (int) FloatMath.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004439 }
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004440 } else {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004441 for (int i = 0; i < mEditor.mCursorCount; i++) {
4442 Rect bounds = mEditor.mCursorDrawable[i].getBounds();
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004443 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
4444 bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
4445 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004446 }
4447 }
4448 }
4449
Gilles Debunned88876a2012-03-16 17:34:04 -07004450 void invalidateCursor() {
Gilles Debunne05336272010-07-09 20:13:45 -07004451 int where = getSelectionEnd();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004452
4453 invalidateCursor(where, where, where);
4454 }
4455
4456 private void invalidateCursor(int a, int b, int c) {
Gilles Debunne8615ac92011-11-29 15:25:03 -08004457 if (a >= 0 || b >= 0 || c >= 0) {
4458 int start = Math.min(Math.min(a, b), c);
4459 int end = Math.max(Math.max(a, b), c);
Gilles Debunne961ebb92011-12-12 10:16:04 -08004460 invalidateRegion(start, end, true /* Also invalidates blinking cursor */);
Gilles Debunne8615ac92011-11-29 15:25:03 -08004461 }
4462 }
4463
4464 /**
4465 * Invalidates the region of text enclosed between the start and end text offsets.
Gilles Debunne8615ac92011-11-29 15:25:03 -08004466 */
Gilles Debunne961ebb92011-12-12 10:16:04 -08004467 void invalidateRegion(int start, int end, boolean invalidateCursor) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004468 if (mLayout == null) {
4469 invalidate();
4470 } else {
Gilles Debunne8615ac92011-11-29 15:25:03 -08004471 int lineStart = mLayout.getLineForOffset(start);
4472 int top = mLayout.getLineTop(lineStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004473
4474 // This is ridiculous, but the descent from the line above
4475 // can hang down into the line we really want to redraw,
4476 // so we have to invalidate part of the line above to make
4477 // sure everything that needs to be redrawn really is.
4478 // (But not the whole line above, because that would cause
4479 // the same problem with the descenders on the line above it!)
Gilles Debunne8615ac92011-11-29 15:25:03 -08004480 if (lineStart > 0) {
4481 top -= mLayout.getLineDescent(lineStart - 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004482 }
4483
Gilles Debunne8615ac92011-11-29 15:25:03 -08004484 int lineEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004485
Gilles Debunne8615ac92011-11-29 15:25:03 -08004486 if (start == end)
4487 lineEnd = lineStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004488 else
Gilles Debunne8615ac92011-11-29 15:25:03 -08004489 lineEnd = mLayout.getLineForOffset(end);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004490
Gilles Debunne8615ac92011-11-29 15:25:03 -08004491 int bottom = mLayout.getLineBottom(lineEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004492
Gilles Debunne83051b82012-02-24 20:01:13 -08004493 // mEditor can be null in case selection is set programmatically.
4494 if (invalidateCursor && mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004495 for (int i = 0; i < mEditor.mCursorCount; i++) {
4496 Rect bounds = mEditor.mCursorDrawable[i].getBounds();
Gilles Debunne961ebb92011-12-12 10:16:04 -08004497 top = Math.min(top, bounds.top);
4498 bottom = Math.max(bottom, bounds.bottom);
4499 }
4500 }
4501
Gilles Debunne8615ac92011-11-29 15:25:03 -08004502 final int compoundPaddingLeft = getCompoundPaddingLeft();
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004503 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
Gilles Debunne8615ac92011-11-29 15:25:03 -08004504
4505 int left, right;
Gilles Debunne961ebb92011-12-12 10:16:04 -08004506 if (lineStart == lineEnd && !invalidateCursor) {
Gilles Debunne8615ac92011-11-29 15:25:03 -08004507 left = (int) mLayout.getPrimaryHorizontal(start);
4508 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0);
4509 left += compoundPaddingLeft;
4510 right += compoundPaddingLeft;
4511 } else {
4512 // Rectangle bounding box when the region spans several lines
4513 left = compoundPaddingLeft;
4514 right = getWidth() - getCompoundPaddingRight();
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004515 }
4516
Gilles Debunne8615ac92011-11-29 15:25:03 -08004517 invalidate(mScrollX + left, verticalPadding + top,
4518 mScrollX + right, verticalPadding + bottom);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004519 }
4520 }
4521
4522 private void registerForPreDraw() {
Gilles Debunne2e37d622012-01-27 13:54:00 -08004523 if (!mPreDrawRegistered) {
4524 getViewTreeObserver().addOnPreDrawListener(this);
4525 mPreDrawRegistered = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004526 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004527 }
4528
4529 /**
4530 * {@inheritDoc}
4531 */
4532 public boolean onPreDraw() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004533 if (mLayout == null) {
4534 assumeLayout();
4535 }
4536
4537 boolean changed = false;
4538
4539 if (mMovement != null) {
Gilles Debunne05336272010-07-09 20:13:45 -07004540 /* This code also provides auto-scrolling when a cursor is moved using a
4541 * CursorController (insertion point or selection limits).
4542 * For selection, ensure start or end is visible depending on controller's state.
4543 */
4544 int curs = getSelectionEnd();
Gilles Debunnee587d832010-11-23 20:20:11 -08004545 // Do not create the controller if it is not already created.
Gilles Debunne2d373a12012-04-20 15:32:19 -07004546 if (mEditor != null && mEditor.mSelectionModifierCursorController != null &&
4547 mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07004548 curs = getSelectionStart();
Gilles Debunne05336272010-07-09 20:13:45 -07004549 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004550
4551 /*
4552 * TODO: This should really only keep the end in view if
4553 * it already was before the text changed. I'm not sure
4554 * of a good way to tell from here if it was.
4555 */
Gilles Debunne60e21862012-01-30 15:04:14 -08004556 if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004557 curs = mText.length();
4558 }
4559
4560 if (curs >= 0) {
4561 changed = bringPointIntoView(curs);
4562 }
4563 } else {
4564 changed = bringTextIntoView();
4565 }
4566
Gilles Debunne64e54a62010-09-07 19:07:17 -07004567 // This has to be checked here since:
4568 // - onFocusChanged cannot start it when focus is given to a view with selected text (after
4569 // a screen rotation) since layout is not yet initialized at that point.
Gilles Debunne2d373a12012-04-20 15:32:19 -07004570 if (mEditor != null && mEditor.mCreatedWithASelection) {
4571 mEditor.startSelectionActionMode();
4572 mEditor.mCreatedWithASelection = false;
Gilles Debunnec01f3fe2010-12-22 17:07:36 -08004573 }
4574
4575 // Phone specific code (there is no ExtractEditText on tablets).
4576 // ExtractEditText does not call onFocus when it is displayed, and mHasSelectionOnFocus can
4577 // not be set. Do the test here instead.
Gilles Debunned88876a2012-03-16 17:34:04 -07004578 if (this instanceof ExtractEditText && hasSelection() && mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004579 mEditor.startSelectionActionMode();
Gilles Debunnef788a9f2010-07-22 10:17:23 -07004580 }
Gilles Debunne64e54a62010-09-07 19:07:17 -07004581
Gilles Debunne2e37d622012-01-27 13:54:00 -08004582 getViewTreeObserver().removeOnPreDrawListener(this);
4583 mPreDrawRegistered = false;
4584
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004585 return !changed;
4586 }
4587
4588 @Override
4589 protected void onAttachedToWindow() {
4590 super.onAttachedToWindow();
4591
4592 mTemporaryDetach = false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07004593
Gilles Debunne2d373a12012-04-20 15:32:19 -07004594 if (mEditor != null) mEditor.onAttachedToWindow();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004595 }
4596
4597 @Override
4598 protected void onDetachedFromWindow() {
4599 super.onDetachedFromWindow();
4600
Gilles Debunne2e37d622012-01-27 13:54:00 -08004601 if (mPreDrawRegistered) {
4602 getViewTreeObserver().removeOnPreDrawListener(this);
4603 mPreDrawRegistered = false;
Gilles Debunne81f08082011-02-17 14:07:19 -08004604 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004605
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07004606 resetResolvedDrawables();
Gilles Debunne186aaf92011-09-16 14:26:12 -07004607
Gilles Debunne2d373a12012-04-20 15:32:19 -07004608 if (mEditor != null) mEditor.onDetachedFromWindow();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004609 }
4610
4611 @Override
Romain Guybb9908b2012-03-08 11:14:07 -08004612 public void onScreenStateChanged(int screenState) {
4613 super.onScreenStateChanged(screenState);
Gilles Debunne2d373a12012-04-20 15:32:19 -07004614 if (mEditor != null) mEditor.onScreenStateChanged(screenState);
Romain Guybb9908b2012-03-08 11:14:07 -08004615 }
4616
4617 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004618 protected boolean isPaddingOffsetRequired() {
Romain Guy076dc9f2009-06-24 17:17:51 -07004619 return mShadowRadius != 0 || mDrawables != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004620 }
4621
4622 @Override
4623 protected int getLeftPaddingOffset() {
Romain Guy076dc9f2009-06-24 17:17:51 -07004624 return getCompoundPaddingLeft() - mPaddingLeft +
4625 (int) Math.min(0, mShadowDx - mShadowRadius);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004626 }
4627
4628 @Override
4629 protected int getTopPaddingOffset() {
4630 return (int) Math.min(0, mShadowDy - mShadowRadius);
4631 }
4632
4633 @Override
4634 protected int getBottomPaddingOffset() {
4635 return (int) Math.max(0, mShadowDy + mShadowRadius);
4636 }
4637
4638 @Override
4639 protected int getRightPaddingOffset() {
Romain Guy076dc9f2009-06-24 17:17:51 -07004640 return -(getCompoundPaddingRight() - mPaddingRight) +
4641 (int) Math.max(0, mShadowDx + mShadowRadius);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004642 }
4643
4644 @Override
4645 protected boolean verifyDrawable(Drawable who) {
4646 final boolean verified = super.verifyDrawable(who);
4647 if (!verified && mDrawables != null) {
4648 return who == mDrawables.mDrawableLeft || who == mDrawables.mDrawableTop ||
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07004649 who == mDrawables.mDrawableRight || who == mDrawables.mDrawableBottom ||
4650 who == mDrawables.mDrawableStart || who == mDrawables.mDrawableEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004651 }
4652 return verified;
4653 }
4654
4655 @Override
Dianne Hackborne2136772010-11-04 15:08:59 -07004656 public void jumpDrawablesToCurrentState() {
4657 super.jumpDrawablesToCurrentState();
4658 if (mDrawables != null) {
4659 if (mDrawables.mDrawableLeft != null) {
4660 mDrawables.mDrawableLeft.jumpToCurrentState();
4661 }
4662 if (mDrawables.mDrawableTop != null) {
4663 mDrawables.mDrawableTop.jumpToCurrentState();
4664 }
4665 if (mDrawables.mDrawableRight != null) {
4666 mDrawables.mDrawableRight.jumpToCurrentState();
4667 }
4668 if (mDrawables.mDrawableBottom != null) {
4669 mDrawables.mDrawableBottom.jumpToCurrentState();
4670 }
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07004671 if (mDrawables.mDrawableStart != null) {
4672 mDrawables.mDrawableStart.jumpToCurrentState();
4673 }
4674 if (mDrawables.mDrawableEnd != null) {
4675 mDrawables.mDrawableEnd.jumpToCurrentState();
4676 }
Dianne Hackborne2136772010-11-04 15:08:59 -07004677 }
4678 }
4679
4680 @Override
Romain Guy3c77d392009-05-20 11:26:50 -07004681 public void invalidateDrawable(Drawable drawable) {
4682 if (verifyDrawable(drawable)) {
4683 final Rect dirty = drawable.getBounds();
4684 int scrollX = mScrollX;
4685 int scrollY = mScrollY;
4686
4687 // IMPORTANT: The coordinates below are based on the coordinates computed
4688 // for each compound drawable in onDraw(). Make sure to update each section
4689 // accordingly.
4690 final TextView.Drawables drawables = mDrawables;
Romain Guya6cd4e02009-05-20 15:09:21 -07004691 if (drawables != null) {
4692 if (drawable == drawables.mDrawableLeft) {
4693 final int compoundPaddingTop = getCompoundPaddingTop();
4694 final int compoundPaddingBottom = getCompoundPaddingBottom();
4695 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
Romain Guy3c77d392009-05-20 11:26:50 -07004696
Romain Guya6cd4e02009-05-20 15:09:21 -07004697 scrollX += mPaddingLeft;
4698 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
4699 } else if (drawable == drawables.mDrawableRight) {
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 += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
4705 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
4706 } else if (drawable == drawables.mDrawableTop) {
4707 final int compoundPaddingLeft = getCompoundPaddingLeft();
4708 final int compoundPaddingRight = getCompoundPaddingRight();
4709 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
Romain Guy3c77d392009-05-20 11:26:50 -07004710
Romain Guya6cd4e02009-05-20 15:09:21 -07004711 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
4712 scrollY += mPaddingTop;
4713 } else if (drawable == drawables.mDrawableBottom) {
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.mDrawableWidthBottom) / 2;
4719 scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
4720 }
Romain Guy3c77d392009-05-20 11:26:50 -07004721 }
4722
4723 invalidate(dirty.left + scrollX, dirty.top + scrollY,
4724 dirty.right + scrollX, dirty.bottom + scrollY);
4725 }
4726 }
4727
4728 @Override
Chet Haasedb8c9a62012-03-21 18:54:18 -07004729 public boolean hasOverlappingRendering() {
Chet Haase1271e2c2012-04-20 09:54:27 -07004730 return (getBackground() != null || mText instanceof Spannable || hasSelection());
Chet Haasedb8c9a62012-03-21 18:54:18 -07004731 }
4732
Gilles Debunne86b9c782010-11-11 10:43:48 -08004733 /**
4734 * When a TextView is used to display a useful piece of information to the user (such as a
4735 * contact's address), it should be made selectable, so that the user can select and copy this
4736 * content.
4737 *
4738 * Use {@link #setTextIsSelectable(boolean)} or the
4739 * {@link android.R.styleable#TextView_textIsSelectable} XML attribute to make this TextView
Gilles Debunnee12f9992010-12-17 11:04:55 -08004740 * selectable (text is not selectable by default).
Gilles Debunne6f100f32010-12-13 18:04:20 -08004741 *
Gilles Debunnebb588da2011-07-11 18:26:19 -07004742 * Note that this method simply returns the state of this flag. Although this flag has to be set
4743 * in order to select text in non-editable TextView, the content of an {@link EditText} can
4744 * always be selected, independently of the value of this flag.
Gilles Debunne86b9c782010-11-11 10:43:48 -08004745 *
4746 * @return True if the text displayed in this TextView can be selected by the user.
4747 *
4748 * @attr ref android.R.styleable#TextView_textIsSelectable
4749 */
4750 public boolean isTextSelectable() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004751 return mEditor == null ? false : mEditor.mTextIsSelectable;
Gilles Debunne86b9c782010-11-11 10:43:48 -08004752 }
4753
4754 /**
4755 * Sets whether or not (default) the content of this view is selectable by the user.
Gilles Debunne60e21862012-01-30 15:04:14 -08004756 *
Gilles Debunnee12f9992010-12-17 11:04:55 -08004757 * Note that this methods affect the {@link #setFocusable(boolean)},
Gilles Debunnecbcb3452010-12-17 15:31:02 -08004758 * {@link #setFocusableInTouchMode(boolean)} {@link #setClickable(boolean)} and
4759 * {@link #setLongClickable(boolean)} states and you may want to restore these if they were
4760 * customized.
Gilles Debunne86b9c782010-11-11 10:43:48 -08004761 *
4762 * See {@link #isTextSelectable} for details.
4763 *
4764 * @param selectable Whether or not the content of this TextView should be selectable.
4765 */
4766 public void setTextIsSelectable(boolean selectable) {
Gilles Debunne60e21862012-01-30 15:04:14 -08004767 if (!selectable && mEditor == null) return; // false is default value with no edit data
Gilles Debunne86b9c782010-11-11 10:43:48 -08004768
Gilles Debunne5fae9962012-05-08 14:53:20 -07004769 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004770 if (mEditor.mTextIsSelectable == selectable) return;
Gilles Debunne86b9c782010-11-11 10:43:48 -08004771
Gilles Debunne2d373a12012-04-20 15:32:19 -07004772 mEditor.mTextIsSelectable = selectable;
Gilles Debunnecbcb3452010-12-17 15:31:02 -08004773 setFocusableInTouchMode(selectable);
Gilles Debunne86b9c782010-11-11 10:43:48 -08004774 setFocusable(selectable);
4775 setClickable(selectable);
4776 setLongClickable(selectable);
4777
Gilles Debunne60e21862012-01-30 15:04:14 -08004778 // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
Gilles Debunne86b9c782010-11-11 10:43:48 -08004779
4780 setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
Gilles Debunne857c3412012-06-07 10:50:58 -07004781 setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
Gilles Debunne86b9c782010-11-11 10:43:48 -08004782
4783 // Called by setText above, but safer in case of future code changes
Gilles Debunne2d373a12012-04-20 15:32:19 -07004784 mEditor.prepareCursorControllers();
Gilles Debunne86b9c782010-11-11 10:43:48 -08004785 }
4786
4787 @Override
4788 protected int[] onCreateDrawableState(int extraSpace) {
Gilles Debunnefb817032011-01-13 13:52:49 -08004789 final int[] drawableState;
Gilles Debunneda0a3f02010-12-22 10:07:15 -08004790
Gilles Debunnefb817032011-01-13 13:52:49 -08004791 if (mSingleLine) {
4792 drawableState = super.onCreateDrawableState(extraSpace);
4793 } else {
4794 drawableState = super.onCreateDrawableState(extraSpace + 1);
Gilles Debunneda0a3f02010-12-22 10:07:15 -08004795 mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
4796 }
4797
Gilles Debunne60e21862012-01-30 15:04:14 -08004798 if (isTextSelectable()) {
Gilles Debunne86b9c782010-11-11 10:43:48 -08004799 // Disable pressed state, which was introduced when TextView was made clickable.
4800 // Prevents text color change.
4801 // setClickable(false) would have a similar effect, but it also disables focus changes
4802 // and long press actions, which are both needed by text selection.
4803 final int length = drawableState.length;
4804 for (int i = 0; i < length; i++) {
4805 if (drawableState[i] == R.attr.state_pressed) {
4806 final int[] nonPressedState = new int[length - 1];
4807 System.arraycopy(drawableState, 0, nonPressedState, 0, i);
4808 System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
4809 return nonPressedState;
4810 }
4811 }
4812 }
Gilles Debunneda0a3f02010-12-22 10:07:15 -08004813
Gilles Debunne86b9c782010-11-11 10:43:48 -08004814 return drawableState;
4815 }
4816
Gilles Debunne83051b82012-02-24 20:01:13 -08004817 private Path getUpdatedHighlightPath() {
4818 Path highlight = null;
4819 Paint highlightPaint = mHighlightPaint;
4820
4821 final int selStart = getSelectionStart();
4822 final int selEnd = getSelectionEnd();
4823 if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
4824 if (selStart == selEnd) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004825 if (mEditor != null && mEditor.isCursorVisible() &&
4826 (SystemClock.uptimeMillis() - mEditor.mShowCursor) %
Gilles Debunned88876a2012-03-16 17:34:04 -07004827 (2 * Editor.BLINK) < Editor.BLINK) {
Gilles Debunne83051b82012-02-24 20:01:13 -08004828 if (mHighlightPathBogus) {
4829 if (mHighlightPath == null) mHighlightPath = new Path();
4830 mHighlightPath.reset();
4831 mLayout.getCursorPath(selStart, mHighlightPath, mText);
Gilles Debunne2d373a12012-04-20 15:32:19 -07004832 mEditor.updateCursorsPositions();
Gilles Debunne83051b82012-02-24 20:01:13 -08004833 mHighlightPathBogus = false;
4834 }
4835
4836 // XXX should pass to skin instead of drawing directly
4837 highlightPaint.setColor(mCurTextColor);
Gilles Debunne83051b82012-02-24 20:01:13 -08004838 highlightPaint.setStyle(Paint.Style.STROKE);
4839 highlight = mHighlightPath;
4840 }
4841 } else {
4842 if (mHighlightPathBogus) {
4843 if (mHighlightPath == null) mHighlightPath = new Path();
4844 mHighlightPath.reset();
4845 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
4846 mHighlightPathBogus = false;
4847 }
4848
4849 // XXX should pass to skin instead of drawing directly
4850 highlightPaint.setColor(mHighlightColor);
Gilles Debunne83051b82012-02-24 20:01:13 -08004851 highlightPaint.setStyle(Paint.Style.FILL);
4852
4853 highlight = mHighlightPath;
4854 }
4855 }
4856 return highlight;
4857 }
4858
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -08004859 /**
4860 * @hide
4861 */
4862 public int getHorizontalOffsetForDrawables() {
4863 return 0;
4864 }
4865
Romain Guyc4d8eb62010-08-18 20:48:33 -07004866 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004867 protected void onDraw(Canvas canvas) {
Romain Guy986003d2009-03-25 17:42:35 -07004868 restartMarqueeIfNeeded();
4869
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004870 // Draw the background for this view
4871 super.onDraw(canvas);
4872
4873 final int compoundPaddingLeft = getCompoundPaddingLeft();
4874 final int compoundPaddingTop = getCompoundPaddingTop();
4875 final int compoundPaddingRight = getCompoundPaddingRight();
4876 final int compoundPaddingBottom = getCompoundPaddingBottom();
4877 final int scrollX = mScrollX;
4878 final int scrollY = mScrollY;
4879 final int right = mRight;
4880 final int left = mLeft;
4881 final int bottom = mBottom;
4882 final int top = mTop;
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -08004883 final boolean isLayoutRtl = isLayoutRtl();
4884 final int offset = getHorizontalOffsetForDrawables();
4885 final int leftOffset = isLayoutRtl ? 0 : offset;
4886 final int rightOffset = isLayoutRtl ? offset : 0 ;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004887
4888 final Drawables dr = mDrawables;
4889 if (dr != null) {
4890 /*
4891 * Compound, not extended, because the icon is not clipped
4892 * if the text height is smaller.
4893 */
4894
4895 int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
4896 int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
4897
Romain Guy3c77d392009-05-20 11:26:50 -07004898 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
4899 // Make sure to update invalidateDrawable() when changing this code.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004900 if (dr.mDrawableLeft != null) {
4901 canvas.save();
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -08004902 canvas.translate(scrollX + mPaddingLeft + leftOffset,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004903 scrollY + compoundPaddingTop +
4904 (vspace - dr.mDrawableHeightLeft) / 2);
4905 dr.mDrawableLeft.draw(canvas);
4906 canvas.restore();
4907 }
4908
Romain Guy3c77d392009-05-20 11:26:50 -07004909 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
4910 // Make sure to update invalidateDrawable() when changing this code.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004911 if (dr.mDrawableRight != null) {
4912 canvas.save();
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -08004913 canvas.translate(scrollX + right - left - mPaddingRight
4914 - dr.mDrawableSizeRight - rightOffset,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004915 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
4916 dr.mDrawableRight.draw(canvas);
4917 canvas.restore();
4918 }
4919
Romain Guy3c77d392009-05-20 11:26:50 -07004920 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
4921 // Make sure to update invalidateDrawable() when changing this code.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004922 if (dr.mDrawableTop != null) {
4923 canvas.save();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004924 canvas.translate(scrollX + compoundPaddingLeft +
4925 (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004926 dr.mDrawableTop.draw(canvas);
4927 canvas.restore();
4928 }
4929
Romain Guy3c77d392009-05-20 11:26:50 -07004930 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
4931 // Make sure to update invalidateDrawable() when changing this code.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004932 if (dr.mDrawableBottom != null) {
4933 canvas.save();
4934 canvas.translate(scrollX + compoundPaddingLeft +
4935 (hspace - dr.mDrawableWidthBottom) / 2,
4936 scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
4937 dr.mDrawableBottom.draw(canvas);
4938 canvas.restore();
4939 }
4940 }
4941
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004942 int color = mCurTextColor;
4943
4944 if (mLayout == null) {
4945 assumeLayout();
4946 }
4947
4948 Layout layout = mLayout;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004949
4950 if (mHint != null && mText.length() == 0) {
4951 if (mHintTextColor != null) {
4952 color = mCurHintTextColor;
4953 }
4954
4955 layout = mHintLayout;
4956 }
4957
4958 mTextPaint.setColor(color);
4959 mTextPaint.drawableState = getDrawableState();
4960
4961 canvas.save();
4962 /* Would be faster if we didn't have to do this. Can we chop the
4963 (displayable) text so that we don't need to do this ever?
4964 */
4965
4966 int extendedPaddingTop = getExtendedPaddingTop();
4967 int extendedPaddingBottom = getExtendedPaddingBottom();
4968
Fabrice Di Meglio132bda12012-02-07 17:02:00 -08004969 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
4970 final int maxScrollY = mLayout.getHeight() - vspace;
4971
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004972 float clipLeft = compoundPaddingLeft + scrollX;
Fabrice Di Meglio132bda12012-02-07 17:02:00 -08004973 float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004974 float clipRight = right - left - compoundPaddingRight + scrollX;
Fabrice Di Meglio132bda12012-02-07 17:02:00 -08004975 float clipBottom = bottom - top + scrollY -
4976 ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004977
4978 if (mShadowRadius != 0) {
4979 clipLeft += Math.min(0, mShadowDx - mShadowRadius);
4980 clipRight += Math.max(0, mShadowDx + mShadowRadius);
4981
4982 clipTop += Math.min(0, mShadowDy - mShadowRadius);
4983 clipBottom += Math.max(0, mShadowDy + mShadowRadius);
4984 }
4985
4986 canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
4987
4988 int voffsetText = 0;
4989 int voffsetCursor = 0;
4990
4991 // translate in by our padding
Gilles Debunne60e21862012-01-30 15:04:14 -08004992 /* shortcircuit calling getVerticaOffset() */
4993 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
4994 voffsetText = getVerticalOffset(false);
4995 voffsetCursor = getVerticalOffset(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004996 }
Gilles Debunne60e21862012-01-30 15:04:14 -08004997 canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004998
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07004999 final int layoutDirection = getLayoutDirection();
Fabrice Di Meglioc0053222011-06-13 12:16:51 -07005000 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
Adam Powell282e3772011-08-30 16:51:11 -07005001 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
5002 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005003 if (!mSingleLine && getLineCount() == 1 && canMarquee() &&
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07005004 (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07005005 final int width = mRight - mLeft;
5006 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
5007 final float dx = mLayout.getLineRight(0) - (width - padding);
5008 canvas.translate(isLayoutRtl ? -dx : +dx, 0.0f);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005009 }
5010
5011 if (mMarquee != null && mMarquee.isRunning()) {
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07005012 final float dx = -mMarquee.getScroll();
5013 canvas.translate(isLayoutRtl ? -dx : +dx, 0.0f);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005014 }
5015 }
5016
Gilles Debunne12d91ce2010-12-10 11:36:29 -08005017 final int cursorOffsetVertical = voffsetCursor - voffsetText;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005018
Gilles Debunne83051b82012-02-24 20:01:13 -08005019 Path highlight = getUpdatedHighlightPath();
Gilles Debunne60e21862012-01-30 15:04:14 -08005020 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005021 mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
Gilles Debunneb35ab7b2011-12-05 15:54:00 -08005022 } else {
Gilles Debunne83051b82012-02-24 20:01:13 -08005023 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
Gilles Debunned88876a2012-03-16 17:34:04 -07005024 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005025
Gilles Debunned88876a2012-03-16 17:34:04 -07005026 if (mMarquee != null && mMarquee.shouldDrawGhost()) {
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07005027 final int dx = (int) mMarquee.getGhostOffset();
5028 canvas.translate(isLayoutRtl ? -dx : dx, 0.0f);
Gilles Debunned88876a2012-03-16 17:34:04 -07005029 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
Romain Guyc2303192009-04-03 17:37:18 -07005030 }
5031
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005032 canvas.restore();
Leon Scroggins56426252010-11-01 15:45:37 -04005033 }
5034
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005035 @Override
5036 public void getFocusedRect(Rect r) {
5037 if (mLayout == null) {
5038 super.getFocusedRect(r);
5039 return;
5040 }
5041
Dianne Hackborn70a3f672011-08-08 14:32:41 -07005042 int selEnd = getSelectionEnd();
5043 if (selEnd < 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005044 super.getFocusedRect(r);
5045 return;
5046 }
5047
Dianne Hackborn70a3f672011-08-08 14:32:41 -07005048 int selStart = getSelectionStart();
5049 if (selStart < 0 || selStart >= selEnd) {
5050 int line = mLayout.getLineForOffset(selEnd);
5051 r.top = mLayout.getLineTop(line);
5052 r.bottom = mLayout.getLineBottom(line);
5053 r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
5054 r.right = r.left + 4;
5055 } else {
5056 int lineStart = mLayout.getLineForOffset(selStart);
5057 int lineEnd = mLayout.getLineForOffset(selEnd);
5058 r.top = mLayout.getLineTop(lineStart);
5059 r.bottom = mLayout.getLineBottom(lineEnd);
5060 if (lineStart == lineEnd) {
5061 r.left = (int) mLayout.getPrimaryHorizontal(selStart);
5062 r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
5063 } else {
Gilles Debunne60e21862012-01-30 15:04:14 -08005064 // Selection extends across multiple lines -- make the focused
5065 // rect cover the entire width.
Gilles Debunne83051b82012-02-24 20:01:13 -08005066 if (mHighlightPathBogus) {
5067 if (mHighlightPath == null) mHighlightPath = new Path();
5068 mHighlightPath.reset();
5069 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
5070 mHighlightPathBogus = false;
5071 }
5072 synchronized (TEMP_RECTF) {
5073 mHighlightPath.computeBounds(TEMP_RECTF, true);
5074 r.left = (int)TEMP_RECTF.left-1;
5075 r.right = (int)TEMP_RECTF.right+1;
Dianne Hackborn70a3f672011-08-08 14:32:41 -07005076 }
5077 }
5078 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005079
5080 // Adjust for padding and gravity.
5081 int paddingLeft = getCompoundPaddingLeft();
5082 int paddingTop = getExtendedPaddingTop();
5083 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5084 paddingTop += getVerticalOffset(false);
5085 }
5086 r.offset(paddingLeft, paddingTop);
Gilles Debunne322044a2012-02-22 12:01:40 -08005087 int paddingBottom = getExtendedPaddingBottom();
5088 r.bottom += paddingBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005089 }
5090
5091 /**
5092 * Return the number of lines of text, or 0 if the internal Layout has not
5093 * been built.
5094 */
5095 public int getLineCount() {
5096 return mLayout != null ? mLayout.getLineCount() : 0;
5097 }
5098
5099 /**
5100 * Return the baseline for the specified line (0...getLineCount() - 1)
5101 * If bounds is not null, return the top, left, right, bottom extents
5102 * of the specified line in it. If the internal Layout has not been built,
5103 * return 0 and set bounds to (0, 0, 0, 0)
5104 * @param line which line to examine (0..getLineCount() - 1)
5105 * @param bounds Optional. If not null, it returns the extent of the line
5106 * @return the Y-coordinate of the baseline
5107 */
5108 public int getLineBounds(int line, Rect bounds) {
5109 if (mLayout == null) {
5110 if (bounds != null) {
5111 bounds.set(0, 0, 0, 0);
5112 }
5113 return 0;
5114 }
5115 else {
5116 int baseline = mLayout.getLineBounds(line, bounds);
5117
5118 int voffset = getExtendedPaddingTop();
5119 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5120 voffset += getVerticalOffset(true);
5121 }
5122 if (bounds != null) {
5123 bounds.offset(getCompoundPaddingLeft(), voffset);
5124 }
5125 return baseline + voffset;
5126 }
5127 }
5128
5129 @Override
5130 public int getBaseline() {
5131 if (mLayout == null) {
5132 return super.getBaseline();
5133 }
5134
5135 int voffset = 0;
5136 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5137 voffset = getVerticalOffset(true);
5138 }
5139
Philip Milne7b757812012-09-19 18:13:44 -07005140 if (isLayoutModeOptical(mParent)) {
5141 voffset -= getOpticalInsets().top;
5142 }
5143
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005144 return getExtendedPaddingTop() + voffset + mLayout.getLineBaseline(0);
5145 }
5146
Romain Guyf2fc4602011-07-19 15:20:03 -07005147 /**
5148 * @hide
Romain Guyf2fc4602011-07-19 15:20:03 -07005149 */
5150 @Override
5151 protected int getFadeTop(boolean offsetRequired) {
Romain Guy59f13c7d2011-07-19 18:35:33 -07005152 if (mLayout == null) return 0;
5153
Romain Guyf2fc4602011-07-19 15:20:03 -07005154 int voffset = 0;
5155 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5156 voffset = getVerticalOffset(true);
5157 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005158
Romain Guyf2fc4602011-07-19 15:20:03 -07005159 if (offsetRequired) voffset += getTopPaddingOffset();
5160
5161 return getExtendedPaddingTop() + voffset;
5162 }
5163
5164 /**
5165 * @hide
Romain Guyf2fc4602011-07-19 15:20:03 -07005166 */
Gilles Debunne3784a7f2011-07-15 13:49:38 -07005167 @Override
Romain Guyf2fc4602011-07-19 15:20:03 -07005168 protected int getFadeHeight(boolean offsetRequired) {
5169 return mLayout != null ? mLayout.getHeight() : 0;
5170 }
5171
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005172 @Override
Gilles Debunne4a7199a2011-07-11 14:58:27 -07005173 public boolean onKeyPreIme(int keyCode, KeyEvent event) {
5174 if (keyCode == KeyEvent.KEYCODE_BACK) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005175 boolean isInSelectionMode = mEditor != null && mEditor.mSelectionActionMode != null;
Gilles Debunne4a7199a2011-07-11 14:58:27 -07005176
Gilles Debunne28294cc2011-08-24 12:02:05 -07005177 if (isInSelectionMode) {
Gilles Debunne4a7199a2011-07-11 14:58:27 -07005178 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
5179 KeyEvent.DispatcherState state = getKeyDispatcherState();
5180 if (state != null) {
5181 state.startTracking(event, this);
5182 }
5183 return true;
5184 } else if (event.getAction() == KeyEvent.ACTION_UP) {
5185 KeyEvent.DispatcherState state = getKeyDispatcherState();
5186 if (state != null) {
5187 state.handleUpEvent(event);
5188 }
5189 if (event.isTracking() && !event.isCanceled()) {
Gilles Debunne14568c32012-01-13 15:26:05 -08005190 stopSelectionActionMode();
5191 return true;
Gilles Debunne4a7199a2011-07-11 14:58:27 -07005192 }
5193 }
5194 }
5195 }
5196 return super.onKeyPreIme(keyCode, event);
5197 }
5198
5199 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005200 public boolean onKeyDown(int keyCode, KeyEvent event) {
5201 int which = doKeyDown(keyCode, event, null);
5202 if (which == 0) {
5203 // Go through default dispatching.
5204 return super.onKeyDown(keyCode, event);
5205 }
5206
5207 return true;
5208 }
5209
5210 @Override
5211 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005212 KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005213
5214 int which = doKeyDown(keyCode, down, event);
5215 if (which == 0) {
5216 // Go through default dispatching.
5217 return super.onKeyMultiple(keyCode, repeatCount, event);
5218 }
5219 if (which == -1) {
5220 // Consumed the whole thing.
5221 return true;
5222 }
5223
5224 repeatCount--;
Gilles Debunne2d373a12012-04-20 15:32:19 -07005225
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005226 // We are going to dispatch the remaining events to either the input
5227 // or movement method. To do this, we will just send a repeated stream
5228 // of down and up events until we have done the complete repeatCount.
5229 // It would be nice if those interfaces had an onKeyMultiple() method,
5230 // but adding that is a more complicated change.
The Android Open Source Project10592532009-03-18 17:39:46 -07005231 KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005232 if (which == 1) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005233 // mEditor and mEditor.mInput are not null from doKeyDown
5234 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005235 while (--repeatCount > 0) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005236 mEditor.mKeyListener.onKeyDown(this, (Editable)mText, keyCode, down);
5237 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005238 }
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08005239 hideErrorIfUnchanged();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005240
5241 } else if (which == 2) {
Gilles Debunne60e21862012-01-30 15:04:14 -08005242 // mMovement is not null from doKeyDown
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005243 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
5244 while (--repeatCount > 0) {
5245 mMovement.onKeyDown(this, (Spannable)mText, keyCode, down);
5246 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
5247 }
5248 }
5249
5250 return true;
5251 }
5252
5253 /**
5254 * Returns true if pressing ENTER in this field advances focus instead
5255 * of inserting the character. This is true mostly in single-line fields,
5256 * but also in mail addresses and subjects which will display on multiple
5257 * lines but where it doesn't make sense to insert newlines.
5258 */
The Android Open Source Project4df24232009-03-05 14:34:35 -08005259 private boolean shouldAdvanceFocusOnEnter() {
Gilles Debunne60e21862012-01-30 15:04:14 -08005260 if (getKeyListener() == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005261 return false;
5262 }
5263
5264 if (mSingleLine) {
5265 return true;
5266 }
5267
Gilles Debunne2d373a12012-04-20 15:32:19 -07005268 if (mEditor != null &&
5269 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
5270 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005271 if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
5272 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005273 return true;
5274 }
5275 }
5276
5277 return false;
5278 }
5279
Jeff Brown4e6319b2010-12-13 10:36:51 -08005280 /**
5281 * Returns true if pressing TAB in this field advances focus instead
5282 * of inserting the character. Insert tabs only in multi-line editors.
5283 */
5284 private boolean shouldAdvanceFocusOnTab() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005285 if (getKeyListener() != null && !mSingleLine && mEditor != null &&
5286 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
5287 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
5288 if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
5289 || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
5290 return false;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005291 }
5292 }
5293 return true;
5294 }
5295
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005296 private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
5297 if (!isEnabled()) {
5298 return 0;
5299 }
5300
5301 switch (keyCode) {
5302 case KeyEvent.KEYCODE_ENTER:
Jeff Brown4e6319b2010-12-13 10:36:51 -08005303 if (event.hasNoModifiers()) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005304 // When mInputContentType is set, we know that we are
5305 // running in a "modern" cupcake environment, so don't need
5306 // to worry about the application trying to capture
5307 // enter key events.
Gilles Debunne2d373a12012-04-20 15:32:19 -07005308 if (mEditor != null && mEditor.mInputContentType != null) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005309 // If there is an action listener, given them a
5310 // chance to consume the event.
Gilles Debunne2d373a12012-04-20 15:32:19 -07005311 if (mEditor.mInputContentType.onEditorActionListener != null &&
5312 mEditor.mInputContentType.onEditorActionListener.onEditorAction(
The Android Open Source Project10592532009-03-18 17:39:46 -07005313 this, EditorInfo.IME_NULL, event)) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005314 mEditor.mInputContentType.enterDown = true;
The Android Open Source Project10592532009-03-18 17:39:46 -07005315 // We are consuming the enter key for them.
5316 return -1;
5317 }
5318 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08005319
The Android Open Source Project10592532009-03-18 17:39:46 -07005320 // If our editor should move focus when enter is pressed, or
5321 // this is a generated event from an IME action button, then
5322 // don't let it be inserted into the text.
Jeff Brown4e6319b2010-12-13 10:36:51 -08005323 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
The Android Open Source Project10592532009-03-18 17:39:46 -07005324 || shouldAdvanceFocusOnEnter()) {
Dianne Hackborn0500b3c2011-11-01 15:28:43 -07005325 if (hasOnClickListeners()) {
Leon Scroggins7014b122011-01-11 15:17:34 -05005326 return 0;
5327 }
The Android Open Source Project10592532009-03-18 17:39:46 -07005328 return -1;
5329 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005330 }
The Android Open Source Project10592532009-03-18 17:39:46 -07005331 break;
Gilles Debunne2d373a12012-04-20 15:32:19 -07005332
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005333 case KeyEvent.KEYCODE_DPAD_CENTER:
Jeff Brown4e6319b2010-12-13 10:36:51 -08005334 if (event.hasNoModifiers()) {
5335 if (shouldAdvanceFocusOnEnter()) {
5336 return 0;
5337 }
5338 }
5339 break;
5340
5341 case KeyEvent.KEYCODE_TAB:
5342 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
5343 if (shouldAdvanceFocusOnTab()) {
5344 return 0;
5345 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005346 }
Gilles Debunne64e54a62010-09-07 19:07:17 -07005347 break;
5348
5349 // Has to be done on key down (and not on key up) to correctly be intercepted.
5350 case KeyEvent.KEYCODE_BACK:
Gilles Debunne2d373a12012-04-20 15:32:19 -07005351 if (mEditor != null && mEditor.mSelectionActionMode != null) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07005352 stopSelectionActionMode();
5353 return -1;
5354 }
5355 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005356 }
5357
Gilles Debunne2d373a12012-04-20 15:32:19 -07005358 if (mEditor != null && mEditor.mKeyListener != null) {
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08005359 resetErrorChangedFlag();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005360
5361 boolean doDown = true;
5362 if (otherEvent != null) {
5363 try {
5364 beginBatchEdit();
Gilles Debunne2d373a12012-04-20 15:32:19 -07005365 final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText,
5366 otherEvent);
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08005367 hideErrorIfUnchanged();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005368 doDown = false;
5369 if (handled) {
5370 return -1;
5371 }
5372 } catch (AbstractMethodError e) {
5373 // onKeyOther was added after 1.0, so if it isn't
5374 // implemented we need to try to dispatch as a regular down.
5375 } finally {
5376 endBatchEdit();
5377 }
5378 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005379
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005380 if (doDown) {
5381 beginBatchEdit();
Gilles Debunne2d373a12012-04-20 15:32:19 -07005382 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText,
5383 keyCode, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005384 endBatchEdit();
Gilles Debunne12ab6452011-01-30 12:08:25 -08005385 hideErrorIfUnchanged();
5386 if (handled) return 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005387 }
5388 }
5389
5390 // bug 650865: sometimes we get a key event before a layout.
5391 // don't try to move around if we don't know the layout.
5392
5393 if (mMovement != null && mLayout != null) {
5394 boolean doDown = true;
5395 if (otherEvent != null) {
5396 try {
5397 boolean handled = mMovement.onKeyOther(this, (Spannable) mText,
5398 otherEvent);
5399 doDown = false;
5400 if (handled) {
5401 return -1;
5402 }
5403 } catch (AbstractMethodError e) {
5404 // onKeyOther was added after 1.0, so if it isn't
5405 // implemented we need to try to dispatch as a regular down.
5406 }
5407 }
5408 if (doDown) {
5409 if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event))
5410 return 2;
5411 }
5412 }
5413
5414 return 0;
5415 }
5416
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08005417 /**
5418 * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
5419 * can be recorded.
5420 * @hide
5421 */
5422 public void resetErrorChangedFlag() {
5423 /*
5424 * Keep track of what the error was before doing the input
5425 * so that if an input filter changed the error, we leave
5426 * that error showing. Otherwise, we take down whatever
5427 * error was showing when the user types something.
5428 */
Gilles Debunne2d373a12012-04-20 15:32:19 -07005429 if (mEditor != null) mEditor.mErrorWasChanged = false;
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08005430 }
5431
5432 /**
5433 * @hide
5434 */
5435 public void hideErrorIfUnchanged() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005436 if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) {
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08005437 setError(null, null);
5438 }
5439 }
5440
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005441 @Override
5442 public boolean onKeyUp(int keyCode, KeyEvent event) {
5443 if (!isEnabled()) {
5444 return super.onKeyUp(keyCode, event);
5445 }
5446
5447 switch (keyCode) {
5448 case KeyEvent.KEYCODE_DPAD_CENTER:
Jeff Brown4e6319b2010-12-13 10:36:51 -08005449 if (event.hasNoModifiers()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005450 /*
5451 * If there is a click listener, just call through to
5452 * super, which will invoke it.
5453 *
Jeff Brown4e6319b2010-12-13 10:36:51 -08005454 * If there isn't a click listener, try to show the soft
5455 * input method. (It will also
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005456 * call performClick(), but that won't do anything in
5457 * this case.)
5458 */
Gilles Debunne06a8e9b2011-12-08 10:39:39 -08005459 if (!hasOnClickListeners()) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08005460 if (mMovement != null && mText instanceof Editable
5461 && mLayout != null && onCheckIsTextEditor()) {
Gilles Debunne17d31de2011-01-27 11:02:18 -08005462 InputMethodManager imm = InputMethodManager.peekInstance();
satoka67a3cf2011-09-07 17:14:03 +09005463 viewClicked(imm);
Gilles Debunne3473b2b2012-04-20 16:21:10 -07005464 if (imm != null && getShowSoftInputOnFocus()) {
satok863fcd62011-06-21 17:38:02 +09005465 imm.showSoftInput(this, 0);
5466 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08005467 }
5468 }
5469 }
5470 return super.onKeyUp(keyCode, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005471
Jeff Brown4e6319b2010-12-13 10:36:51 -08005472 case KeyEvent.KEYCODE_ENTER:
Jeff Brown4e6319b2010-12-13 10:36:51 -08005473 if (event.hasNoModifiers()) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005474 if (mEditor != null && mEditor.mInputContentType != null
5475 && mEditor.mInputContentType.onEditorActionListener != null
5476 && mEditor.mInputContentType.enterDown) {
5477 mEditor.mInputContentType.enterDown = false;
5478 if (mEditor.mInputContentType.onEditorActionListener.onEditorAction(
Jeff Brown4e6319b2010-12-13 10:36:51 -08005479 this, EditorInfo.IME_NULL, event)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005480 return true;
5481 }
5482 }
5483
Jeff Brown4e6319b2010-12-13 10:36:51 -08005484 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
5485 || shouldAdvanceFocusOnEnter()) {
5486 /*
5487 * If there is a click listener, just call through to
5488 * super, which will invoke it.
5489 *
5490 * If there isn't a click listener, try to advance focus,
5491 * but still call through to super, which will reset the
5492 * pressed state and longpress state. (It will also
5493 * call performClick(), but that won't do anything in
5494 * this case.)
5495 */
Gilles Debunne06a8e9b2011-12-08 10:39:39 -08005496 if (!hasOnClickListeners()) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08005497 View v = focusSearch(FOCUS_DOWN);
5498
5499 if (v != null) {
5500 if (!v.requestFocus(FOCUS_DOWN)) {
5501 throw new IllegalStateException(
5502 "focus search returned a view " +
5503 "that wasn't able to take focus!");
5504 }
5505
5506 /*
5507 * Return true because we handled the key; super
5508 * will return false because there was no click
5509 * listener.
5510 */
5511 super.onKeyUp(keyCode, event);
5512 return true;
5513 } else if ((event.getFlags()
5514 & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
5515 // No target for next focus, but make sure the IME
5516 // if this came from it.
5517 InputMethodManager imm = InputMethodManager.peekInstance();
Gilles Debunne17d31de2011-01-27 11:02:18 -08005518 if (imm != null && imm.isActive(this)) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08005519 imm.hideSoftInputFromWindow(getWindowToken(), 0);
5520 }
5521 }
5522 }
5523 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005524 return super.onKeyUp(keyCode, event);
5525 }
Gilles Debunne64e54a62010-09-07 19:07:17 -07005526 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005527 }
5528
Gilles Debunne2d373a12012-04-20 15:32:19 -07005529 if (mEditor != null && mEditor.mKeyListener != null)
5530 if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event))
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005531 return true;
5532
5533 if (mMovement != null && mLayout != null)
5534 if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event))
5535 return true;
5536
5537 return super.onKeyUp(keyCode, event);
5538 }
5539
Gilles Debunnec1714022012-01-17 13:59:23 -08005540 @Override
5541 public boolean onCheckIsTextEditor() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005542 return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005543 }
Gilles Debunneb062e812011-09-27 14:58:37 -07005544
Gilles Debunnec1714022012-01-17 13:59:23 -08005545 @Override
5546 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
Janos Levai042856c2010-10-15 02:53:58 +03005547 if (onCheckIsTextEditor() && isEnabled()) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005548 mEditor.createInputMethodStateIfNeeded();
Gilles Debunne60e21862012-01-30 15:04:14 -08005549 outAttrs.inputType = getInputType();
Gilles Debunne2d373a12012-04-20 15:32:19 -07005550 if (mEditor.mInputContentType != null) {
5551 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
5552 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
5553 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
5554 outAttrs.actionId = mEditor.mInputContentType.imeActionId;
5555 outAttrs.extras = mEditor.mInputContentType.extras;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005556 } else {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005557 outAttrs.imeOptions = EditorInfo.IME_NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005558 }
Dianne Hackborndea3ef72010-10-28 14:24:22 -07005559 if (focusSearch(FOCUS_DOWN) != null) {
5560 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
5561 }
5562 if (focusSearch(FOCUS_UP) != null) {
5563 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
5564 }
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005565 if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION)
5566 == EditorInfo.IME_ACTION_UNSPECIFIED) {
Dianne Hackborndea3ef72010-10-28 14:24:22 -07005567 if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005568 // An action has not been set, but the enter key will move to
5569 // the next focus, so set the action to that.
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005570 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
The Android Open Source Project4df24232009-03-05 14:34:35 -08005571 } else {
5572 // An action has not been set, and there is no focus to move
5573 // to, so let's just supply a "done" action.
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005574 outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
The Android Open Source Project4df24232009-03-05 14:34:35 -08005575 }
5576 if (!shouldAdvanceFocusOnEnter()) {
5577 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005578 }
5579 }
Gilles Debunne91a08cf2010-11-08 17:34:49 -08005580 if (isMultilineInputType(outAttrs.inputType)) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005581 // Multi-line text editors should always show an enter key.
5582 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
5583 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005584 outAttrs.hintText = mHint;
5585 if (mText instanceof Editable) {
5586 InputConnection ic = new EditableInputConnection(this);
Gilles Debunne05336272010-07-09 20:13:45 -07005587 outAttrs.initialSelStart = getSelectionStart();
5588 outAttrs.initialSelEnd = getSelectionEnd();
Gilles Debunne60e21862012-01-30 15:04:14 -08005589 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005590 return ic;
5591 }
5592 }
5593 return null;
5594 }
5595
5596 /**
5597 * If this TextView contains editable content, extract a portion of it
5598 * based on the information in <var>request</var> in to <var>outText</var>.
5599 * @return Returns true if the text was successfully extracted, else false.
5600 */
Gilles Debunned88876a2012-03-16 17:34:04 -07005601 public boolean extractText(ExtractedTextRequest request, ExtractedText outText) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07005602 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07005603 return mEditor.extractText(request, outText);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005604 }
Viktor Yakovel964be412010-02-17 08:35:57 +01005605
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005606 /**
5607 * This is used to remove all style-impacting spans from text before new
5608 * extracted text is being replaced into it, so that we don't have any
5609 * lingering spans applied during the replace.
5610 */
5611 static void removeParcelableSpans(Spannable spannable, int start, int end) {
5612 Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
5613 int i = spans.length;
5614 while (i > 0) {
5615 i--;
5616 spannable.removeSpan(spans[i]);
5617 }
5618 }
Gilles Debunned88876a2012-03-16 17:34:04 -07005619
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005620 /**
5621 * Apply to this text view the given extracted text, as previously
5622 * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
5623 */
5624 public void setExtractedText(ExtractedText text) {
5625 Editable content = getEditableText();
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005626 if (text.text != null) {
5627 if (content == null) {
5628 setText(text.text, TextView.BufferType.EDITABLE);
5629 } else if (text.partialStartOffset < 0) {
5630 removeParcelableSpans(content, 0, content.length());
5631 content.replace(0, content.length(), text.text);
5632 } else {
5633 final int N = content.length();
5634 int start = text.partialStartOffset;
5635 if (start > N) start = N;
5636 int end = text.partialEndOffset;
5637 if (end > N) end = N;
5638 removeParcelableSpans(content, start, end);
5639 content.replace(start, end, text.text);
5640 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005641 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005642
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005643 // Now set the selection position... make sure it is in range, to
5644 // avoid crashes. If this is a partial update, it is possible that
5645 // the underlying text may have changed, causing us problems here.
5646 // Also we just don't want to trust clients to do the right thing.
5647 Spannable sp = (Spannable)getText();
5648 final int N = sp.length();
5649 int start = text.selectionStart;
5650 if (start < 0) start = 0;
5651 else if (start > N) start = N;
5652 int end = text.selectionEnd;
5653 if (end < 0) end = 0;
5654 else if (end > N) end = N;
5655 Selection.setSelection(sp, start, end);
Gilles Debunne2d373a12012-04-20 15:32:19 -07005656
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005657 // Finally, update the selection mode.
5658 if ((text.flags&ExtractedText.FLAG_SELECTING) != 0) {
5659 MetaKeyKeyListener.startSelecting(this, sp);
5660 } else {
5661 MetaKeyKeyListener.stopSelecting(this, sp);
5662 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005663 }
Gilles Debunne98fb9ed2011-09-07 17:15:41 -07005664
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005665 /**
5666 * @hide
5667 */
5668 public void setExtracting(ExtractedTextRequest req) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005669 if (mEditor.mInputMethodState != null) {
5670 mEditor.mInputMethodState.mExtractedTextRequest = req;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005671 }
Gilles Debunne98fb9ed2011-09-07 17:15:41 -07005672 // This would stop a possible selection mode, but no such mode is started in case
5673 // extracted mode will start. Some text is selected though, and will trigger an action mode
5674 // in the extracted view.
Gilles Debunne2d373a12012-04-20 15:32:19 -07005675 mEditor.hideControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005676 }
Gilles Debunne98fb9ed2011-09-07 17:15:41 -07005677
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005678 /**
5679 * Called by the framework in response to a text completion from
5680 * the current input method, provided by it calling
5681 * {@link InputConnection#commitCompletion
5682 * InputConnection.commitCompletion()}. The default implementation does
5683 * nothing; text views that are supporting auto-completion should override
5684 * this to do their desired behavior.
5685 *
5686 * @param text The auto complete text the user has selected.
5687 */
5688 public void onCommitCompletion(CompletionInfo text) {
Gilles Debunned40cc3c2011-03-11 16:59:32 -08005689 // intentionally empty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005690 }
5691
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08005692 /**
5693 * Called by the framework in response to a text auto-correction (such as fixing a typo using a
5694 * a dictionnary) from the current input method, provided by it calling
5695 * {@link InputConnection#commitCorrection} InputConnection.commitCorrection()}. The default
5696 * implementation flashes the background of the corrected word to provide feedback to the user.
5697 *
5698 * @param info The auto correct info about the text that was corrected.
5699 */
5700 public void onCommitCorrection(CorrectionInfo info) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005701 if (mEditor != null) mEditor.onCommitCorrection(info);
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08005702 }
5703
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005704 public void beginBatchEdit() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005705 if (mEditor != null) mEditor.beginBatchEdit();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005706 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005707
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005708 public void endBatchEdit() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005709 if (mEditor != null) mEditor.endBatchEdit();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005710 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005711
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005712 /**
5713 * Called by the framework in response to a request to begin a batch
5714 * of edit operations through a call to link {@link #beginBatchEdit()}.
5715 */
5716 public void onBeginBatchEdit() {
Gilles Debunned40cc3c2011-03-11 16:59:32 -08005717 // intentionally empty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005718 }
Gilles Debunne60e21862012-01-30 15:04:14 -08005719
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005720 /**
5721 * Called by the framework in response to a request to end a batch
5722 * of edit operations through a call to link {@link #endBatchEdit}.
5723 */
5724 public void onEndBatchEdit() {
Gilles Debunned40cc3c2011-03-11 16:59:32 -08005725 // intentionally empty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005726 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005727
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005728 /**
5729 * Called by the framework in response to a private command from the
5730 * current method, provided by it calling
5731 * {@link InputConnection#performPrivateCommand
5732 * InputConnection.performPrivateCommand()}.
5733 *
5734 * @param action The action name of the command.
5735 * @param data Any additional data for the command. This may be null.
5736 * @return Return true if you handled the command, else false.
5737 */
5738 public boolean onPrivateIMECommand(String action, Bundle data) {
5739 return false;
5740 }
5741
5742 private void nullLayouts() {
5743 if (mLayout instanceof BoringLayout && mSavedLayout == null) {
5744 mSavedLayout = (BoringLayout) mLayout;
5745 }
5746 if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
5747 mSavedHintLayout = (BoringLayout) mHintLayout;
5748 }
5749
Adam Powell282e3772011-08-30 16:51:11 -07005750 mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
Gilles Debunne77f18b02010-10-22 14:28:25 -07005751
Fabrice Di Megliod4c3b8e2011-11-09 18:04:07 -08005752 mBoring = mHintBoring = null;
5753
Gilles Debunne77f18b02010-10-22 14:28:25 -07005754 // Since it depends on the value of mLayout
Gilles Debunne2d373a12012-04-20 15:32:19 -07005755 if (mEditor != null) mEditor.prepareCursorControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005756 }
5757
5758 /**
5759 * Make a new Layout based on the already-measured size of the view,
5760 * on the assumption that it was measured correctly at some point.
5761 */
5762 private void assumeLayout() {
5763 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
5764
5765 if (width < 1) {
5766 width = 0;
5767 }
5768
5769 int physicalWidth = width;
5770
5771 if (mHorizontallyScrolling) {
Jeff Brown033a0012011-11-11 15:30:16 -08005772 width = VERY_WIDE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005773 }
5774
5775 makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
5776 physicalWidth, false);
5777 }
5778
Fabrice Di Meglio9da0f8a2012-03-13 19:37:57 -07005779 @Override
Fabrice Di Meglio343e1132012-09-28 18:01:17 -07005780 public void onRtlPropertiesChanged(int layoutDirection) {
Fabrice Di Meglio9da0f8a2012-03-13 19:37:57 -07005781 if (mLayoutAlignment != null) {
Fabrice Di Megliofa1babd2012-09-04 19:11:25 -07005782 if (mResolvedTextAlignment == TEXT_ALIGNMENT_VIEW_START ||
5783 mResolvedTextAlignment == TEXT_ALIGNMENT_VIEW_END) {
Fabrice Di Meglio9da0f8a2012-03-13 19:37:57 -07005784 mLayoutAlignment = null;
5785 }
5786 }
5787 }
5788
Doug Feltc0ccf0c2011-06-23 16:13:18 -07005789 private Layout.Alignment getLayoutAlignment() {
5790 if (mLayoutAlignment == null) {
Fabrice Di Meglio1a7d4872012-09-23 16:19:58 -07005791 mResolvedTextAlignment = getTextAlignment();
Fabrice Di Megliofa1babd2012-09-04 19:11:25 -07005792 switch (mResolvedTextAlignment) {
Fabrice Di Meglio9da0f8a2012-03-13 19:37:57 -07005793 case TEXT_ALIGNMENT_GRAVITY:
5794 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
5795 case Gravity.START:
5796 mLayoutAlignment = Layout.Alignment.ALIGN_NORMAL;
5797 break;
5798 case Gravity.END:
5799 mLayoutAlignment = Layout.Alignment.ALIGN_OPPOSITE;
5800 break;
5801 case Gravity.LEFT:
5802 mLayoutAlignment = Layout.Alignment.ALIGN_LEFT;
5803 break;
5804 case Gravity.RIGHT:
5805 mLayoutAlignment = Layout.Alignment.ALIGN_RIGHT;
5806 break;
5807 case Gravity.CENTER_HORIZONTAL:
5808 mLayoutAlignment = Layout.Alignment.ALIGN_CENTER;
5809 break;
5810 default:
5811 mLayoutAlignment = Layout.Alignment.ALIGN_NORMAL;
5812 break;
5813 }
5814 break;
5815 case TEXT_ALIGNMENT_TEXT_START:
Gilles Debunne978a8ff2012-02-22 14:27:12 -08005816 mLayoutAlignment = Layout.Alignment.ALIGN_NORMAL;
Doug Feltc0ccf0c2011-06-23 16:13:18 -07005817 break;
Fabrice Di Meglio9da0f8a2012-03-13 19:37:57 -07005818 case TEXT_ALIGNMENT_TEXT_END:
Gilles Debunne978a8ff2012-02-22 14:27:12 -08005819 mLayoutAlignment = Layout.Alignment.ALIGN_OPPOSITE;
Doug Feltc0ccf0c2011-06-23 16:13:18 -07005820 break;
Fabrice Di Meglio9da0f8a2012-03-13 19:37:57 -07005821 case TEXT_ALIGNMENT_CENTER:
Gilles Debunne978a8ff2012-02-22 14:27:12 -08005822 mLayoutAlignment = Layout.Alignment.ALIGN_CENTER;
Doug Feltc0ccf0c2011-06-23 16:13:18 -07005823 break;
Fabrice Di Meglio9da0f8a2012-03-13 19:37:57 -07005824 case TEXT_ALIGNMENT_VIEW_START:
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07005825 mLayoutAlignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
Fabrice Di Meglio9da0f8a2012-03-13 19:37:57 -07005826 Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
5827 break;
5828 case TEXT_ALIGNMENT_VIEW_END:
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07005829 mLayoutAlignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
Fabrice Di Meglio9da0f8a2012-03-13 19:37:57 -07005830 Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
5831 break;
5832 case TEXT_ALIGNMENT_INHERIT:
5833 // This should never happen as we have already resolved the text alignment
5834 // but better safe than sorry so we just fall through
Doug Feltc0ccf0c2011-06-23 16:13:18 -07005835 default:
Gilles Debunne978a8ff2012-02-22 14:27:12 -08005836 mLayoutAlignment = Layout.Alignment.ALIGN_NORMAL;
Doug Feltc0ccf0c2011-06-23 16:13:18 -07005837 break;
5838 }
Doug Feltc0ccf0c2011-06-23 16:13:18 -07005839 }
5840 return mLayoutAlignment;
5841 }
5842
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005843 /**
5844 * The width passed in is now the desired layout width,
5845 * not the full view width with padding.
5846 * {@hide}
5847 */
Gilles Debunne287d6c62011-10-05 18:22:11 -07005848 protected void makeNewLayout(int wantWidth, int hintWidth,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005849 BoringLayout.Metrics boring,
5850 BoringLayout.Metrics hintBoring,
5851 int ellipsisWidth, boolean bringIntoView) {
5852 stopMarquee();
5853
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07005854 // Update "old" cached values
5855 mOldMaximum = mMaximum;
5856 mOldMaxMode = mMaxMode;
5857
Gilles Debunne83051b82012-02-24 20:01:13 -08005858 mHighlightPathBogus = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005859
Gilles Debunne287d6c62011-10-05 18:22:11 -07005860 if (wantWidth < 0) {
5861 wantWidth = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005862 }
5863 if (hintWidth < 0) {
5864 hintWidth = 0;
5865 }
5866
Doug Feltc0ccf0c2011-06-23 16:13:18 -07005867 Layout.Alignment alignment = getLayoutAlignment();
Gilles Debunne60e21862012-01-30 15:04:14 -08005868 boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
Adam Powell282e3772011-08-30 16:51:11 -07005869 final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE &&
5870 mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
5871 TruncateAt effectiveEllipsize = mEllipsize;
5872 if (mEllipsize == TruncateAt.MARQUEE &&
5873 mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
Fabrice Di Megliocb332642011-09-23 19:08:04 -07005874 effectiveEllipsize = TruncateAt.END_SMALL;
Adam Powell282e3772011-08-30 16:51:11 -07005875 }
Romain Guy4dc4f732009-06-19 15:16:40 -07005876
Doug Feltcb3791202011-07-07 11:57:48 -07005877 if (mTextDir == null) {
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07005878 mTextDir = getTextDirectionHeuristic();
Doug Feltcb3791202011-07-07 11:57:48 -07005879 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005880
Gilles Debunne287d6c62011-10-05 18:22:11 -07005881 mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
Adam Powell282e3772011-08-30 16:51:11 -07005882 effectiveEllipsize, effectiveEllipsize == mEllipsize);
5883 if (switchEllipsize) {
5884 TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE ?
5885 TruncateAt.END : TruncateAt.MARQUEE;
Gilles Debunne287d6c62011-10-05 18:22:11 -07005886 mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
Adam Powell282e3772011-08-30 16:51:11 -07005887 shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005888 }
5889
Romain Guy4dc4f732009-06-19 15:16:40 -07005890 shouldEllipsize = mEllipsize != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005891 mHintLayout = null;
5892
5893 if (mHint != null) {
Gilles Debunne287d6c62011-10-05 18:22:11 -07005894 if (shouldEllipsize) hintWidth = wantWidth;
Romain Guy4dc4f732009-06-19 15:16:40 -07005895
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005896 if (hintBoring == UNKNOWN_BORING) {
Doug Feltcb3791202011-07-07 11:57:48 -07005897 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005898 mHintBoring);
5899 if (hintBoring != null) {
5900 mHintBoring = hintBoring;
5901 }
5902 }
5903
5904 if (hintBoring != null) {
Romain Guy4dc4f732009-06-19 15:16:40 -07005905 if (hintBoring.width <= hintWidth &&
5906 (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005907 if (mSavedHintLayout != null) {
5908 mHintLayout = mSavedHintLayout.
5909 replaceOrMake(mHint, mTextPaint,
Romain Guy4dc4f732009-06-19 15:16:40 -07005910 hintWidth, alignment, mSpacingMult, mSpacingAdd,
5911 hintBoring, mIncludePad);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005912 } else {
5913 mHintLayout = BoringLayout.make(mHint, mTextPaint,
Romain Guy4dc4f732009-06-19 15:16:40 -07005914 hintWidth, alignment, mSpacingMult, mSpacingAdd,
5915 hintBoring, mIncludePad);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005916 }
5917
5918 mSavedHintLayout = (BoringLayout) mHintLayout;
Romain Guy4dc4f732009-06-19 15:16:40 -07005919 } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
5920 if (mSavedHintLayout != null) {
5921 mHintLayout = mSavedHintLayout.
5922 replaceOrMake(mHint, mTextPaint,
5923 hintWidth, alignment, mSpacingMult, mSpacingAdd,
5924 hintBoring, mIncludePad, mEllipsize,
5925 ellipsisWidth);
5926 } else {
5927 mHintLayout = BoringLayout.make(mHint, mTextPaint,
5928 hintWidth, alignment, mSpacingMult, mSpacingAdd,
5929 hintBoring, mIncludePad, mEllipsize,
5930 ellipsisWidth);
5931 }
5932 } else if (shouldEllipsize) {
5933 mHintLayout = new StaticLayout(mHint,
5934 0, mHint.length(),
Doug Feltcb3791202011-07-07 11:57:48 -07005935 mTextPaint, hintWidth, alignment, mTextDir, mSpacingMult,
Romain Guy4dc4f732009-06-19 15:16:40 -07005936 mSpacingAdd, mIncludePad, mEllipsize,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07005937 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005938 } else {
5939 mHintLayout = new StaticLayout(mHint, mTextPaint,
Doug Feltcb3791202011-07-07 11:57:48 -07005940 hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005941 mIncludePad);
5942 }
Romain Guy4dc4f732009-06-19 15:16:40 -07005943 } else if (shouldEllipsize) {
5944 mHintLayout = new StaticLayout(mHint,
5945 0, mHint.length(),
Doug Feltcb3791202011-07-07 11:57:48 -07005946 mTextPaint, hintWidth, alignment, mTextDir, mSpacingMult,
Romain Guy4dc4f732009-06-19 15:16:40 -07005947 mSpacingAdd, mIncludePad, mEllipsize,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07005948 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005949 } else {
5950 mHintLayout = new StaticLayout(mHint, mTextPaint,
Doug Feltcb3791202011-07-07 11:57:48 -07005951 hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005952 mIncludePad);
5953 }
5954 }
5955
5956 if (bringIntoView) {
5957 registerForPreDraw();
5958 }
5959
5960 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
Romain Guy939151f2009-04-08 14:22:40 -07005961 if (!compressText(ellipsisWidth)) {
5962 final int height = mLayoutParams.height;
5963 // If the size of the view does not depend on the size of the text, try to
5964 // start the marquee immediately
Romain Guy980a9382010-01-08 15:06:28 -08005965 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
Romain Guy939151f2009-04-08 14:22:40 -07005966 startMarquee();
5967 } else {
5968 // Defer the start of the marquee until we know our width (see setFrame())
5969 mRestartMarquee = true;
5970 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005971 }
5972 }
Gilles Debunnef788a9f2010-07-22 10:17:23 -07005973
5974 // CursorControllers need a non-null mLayout
Gilles Debunne2d373a12012-04-20 15:32:19 -07005975 if (mEditor != null) mEditor.prepareCursorControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005976 }
5977
Gilles Debunne287d6c62011-10-05 18:22:11 -07005978 private Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
Adam Powell282e3772011-08-30 16:51:11 -07005979 Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
5980 boolean useSaved) {
5981 Layout result = null;
5982 if (mText instanceof Spannable) {
Gilles Debunne287d6c62011-10-05 18:22:11 -07005983 result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth,
Adam Powell282e3772011-08-30 16:51:11 -07005984 alignment, mTextDir, mSpacingMult,
Gilles Debunne60e21862012-01-30 15:04:14 -08005985 mSpacingAdd, mIncludePad, getKeyListener() == null ? effectiveEllipsize : null,
Fabrice Di Meglioad0b0512011-10-04 17:21:26 -07005986 ellipsisWidth);
Adam Powell282e3772011-08-30 16:51:11 -07005987 } else {
5988 if (boring == UNKNOWN_BORING) {
5989 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
5990 if (boring != null) {
5991 mBoring = boring;
5992 }
5993 }
5994
5995 if (boring != null) {
Gilles Debunne287d6c62011-10-05 18:22:11 -07005996 if (boring.width <= wantWidth &&
Adam Powell282e3772011-08-30 16:51:11 -07005997 (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
5998 if (useSaved && mSavedLayout != null) {
5999 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006000 wantWidth, alignment, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006001 boring, mIncludePad);
6002 } else {
6003 result = BoringLayout.make(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006004 wantWidth, alignment, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006005 boring, mIncludePad);
6006 }
6007
6008 if (useSaved) {
6009 mSavedLayout = (BoringLayout) result;
6010 }
Gilles Debunne287d6c62011-10-05 18:22:11 -07006011 } else if (shouldEllipsize && boring.width <= wantWidth) {
Adam Powell282e3772011-08-30 16:51:11 -07006012 if (useSaved && mSavedLayout != null) {
6013 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006014 wantWidth, alignment, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006015 boring, mIncludePad, effectiveEllipsize,
6016 ellipsisWidth);
6017 } else {
6018 result = BoringLayout.make(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006019 wantWidth, alignment, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006020 boring, mIncludePad, effectiveEllipsize,
6021 ellipsisWidth);
6022 }
6023 } else if (shouldEllipsize) {
6024 result = new StaticLayout(mTransformed,
6025 0, mTransformed.length(),
Gilles Debunne287d6c62011-10-05 18:22:11 -07006026 mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult,
Adam Powell282e3772011-08-30 16:51:11 -07006027 mSpacingAdd, mIncludePad, effectiveEllipsize,
6028 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6029 } else {
6030 result = new StaticLayout(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006031 wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006032 mIncludePad);
6033 }
6034 } else if (shouldEllipsize) {
6035 result = new StaticLayout(mTransformed,
6036 0, mTransformed.length(),
Gilles Debunne287d6c62011-10-05 18:22:11 -07006037 mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult,
Adam Powell282e3772011-08-30 16:51:11 -07006038 mSpacingAdd, mIncludePad, effectiveEllipsize,
6039 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6040 } else {
6041 result = new StaticLayout(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006042 wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006043 mIncludePad);
6044 }
6045 }
6046 return result;
6047 }
6048
Romain Guy939151f2009-04-08 14:22:40 -07006049 private boolean compressText(float width) {
Romain Guy2bffd262010-09-12 17:40:02 -07006050 if (isHardwareAccelerated()) return false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07006051
Romain Guy3373ed62009-05-04 14:13:32 -07006052 // Only compress the text if it hasn't been compressed by the previous pass
6053 if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX &&
6054 mTextPaint.getTextScaleX() == 1.0f) {
Romain Guy939151f2009-04-08 14:22:40 -07006055 final float textWidth = mLayout.getLineWidth(0);
Romain Guy3373ed62009-05-04 14:13:32 -07006056 final float overflow = (textWidth + 1.0f - width) / width;
Romain Guy939151f2009-04-08 14:22:40 -07006057 if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
6058 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
6059 post(new Runnable() {
6060 public void run() {
6061 requestLayout();
6062 }
6063 });
6064 return true;
6065 }
6066 }
6067
6068 return false;
6069 }
6070
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006071 private static int desired(Layout layout) {
6072 int n = layout.getLineCount();
6073 CharSequence text = layout.getText();
6074 float max = 0;
6075
6076 // if any line was wrapped, we can't use it.
6077 // but it's ok for the last line not to have a newline
6078
6079 for (int i = 0; i < n - 1; i++) {
6080 if (text.charAt(layout.getLineEnd(i) - 1) != '\n')
6081 return -1;
6082 }
6083
6084 for (int i = 0; i < n; i++) {
6085 max = Math.max(max, layout.getLineWidth(i));
6086 }
6087
6088 return (int) FloatMath.ceil(max);
6089 }
6090
6091 /**
6092 * Set whether the TextView includes extra top and bottom padding to make
6093 * room for accents that go above the normal ascent and descent.
6094 * The default is true.
6095 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07006096 * @see #getIncludeFontPadding()
6097 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006098 * @attr ref android.R.styleable#TextView_includeFontPadding
6099 */
6100 public void setIncludeFontPadding(boolean includepad) {
Gilles Debunne22378292011-08-12 10:38:52 -07006101 if (mIncludePad != includepad) {
6102 mIncludePad = includepad;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006103
Gilles Debunne22378292011-08-12 10:38:52 -07006104 if (mLayout != null) {
6105 nullLayouts();
6106 requestLayout();
6107 invalidate();
6108 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006109 }
6110 }
6111
Gilles Debunnef03acef2012-04-30 19:26:19 -07006112 /**
6113 * Gets whether the TextView includes extra top and bottom padding to make
6114 * room for accents that go above the normal ascent and descent.
6115 *
6116 * @see #setIncludeFontPadding(boolean)
6117 *
6118 * @attr ref android.R.styleable#TextView_includeFontPadding
6119 */
6120 public boolean getIncludeFontPadding() {
6121 return mIncludePad;
6122 }
6123
Romain Guy4dc4f732009-06-19 15:16:40 -07006124 private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006125
6126 @Override
6127 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
6128 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
6129 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
6130 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
6131 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
6132
6133 int width;
6134 int height;
6135
6136 BoringLayout.Metrics boring = UNKNOWN_BORING;
6137 BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
6138
Doug Feltcb3791202011-07-07 11:57:48 -07006139 if (mTextDir == null) {
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07006140 getTextDirectionHeuristic();
Doug Feltcb3791202011-07-07 11:57:48 -07006141 }
6142
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006143 int des = -1;
6144 boolean fromexisting = false;
6145
6146 if (widthMode == MeasureSpec.EXACTLY) {
6147 // Parent has told us how big to be. So be it.
6148 width = widthSize;
6149 } else {
6150 if (mLayout != null && mEllipsize == null) {
6151 des = desired(mLayout);
6152 }
6153
6154 if (des < 0) {
Doug Feltcb3791202011-07-07 11:57:48 -07006155 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006156 if (boring != null) {
6157 mBoring = boring;
6158 }
6159 } else {
6160 fromexisting = true;
6161 }
6162
6163 if (boring == null || boring == UNKNOWN_BORING) {
6164 if (des < 0) {
Romain Guy4dc4f732009-06-19 15:16:40 -07006165 des = (int) FloatMath.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006166 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006167 width = des;
6168 } else {
6169 width = boring.width;
6170 }
6171
6172 final Drawables dr = mDrawables;
6173 if (dr != null) {
6174 width = Math.max(width, dr.mDrawableWidthTop);
6175 width = Math.max(width, dr.mDrawableWidthBottom);
6176 }
6177
6178 if (mHint != null) {
6179 int hintDes = -1;
6180 int hintWidth;
6181
Romain Guy4dc4f732009-06-19 15:16:40 -07006182 if (mHintLayout != null && mEllipsize == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006183 hintDes = desired(mHintLayout);
6184 }
6185
6186 if (hintDes < 0) {
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006187 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006188 if (hintBoring != null) {
6189 mHintBoring = hintBoring;
6190 }
6191 }
6192
6193 if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
6194 if (hintDes < 0) {
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006195 hintDes = (int) FloatMath.ceil(Layout.getDesiredWidth(mHint, mTextPaint));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006196 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006197 hintWidth = hintDes;
6198 } else {
6199 hintWidth = hintBoring.width;
6200 }
6201
6202 if (hintWidth > width) {
6203 width = hintWidth;
6204 }
6205 }
6206
6207 width += getCompoundPaddingLeft() + getCompoundPaddingRight();
6208
6209 if (mMaxWidthMode == EMS) {
6210 width = Math.min(width, mMaxWidth * getLineHeight());
6211 } else {
6212 width = Math.min(width, mMaxWidth);
6213 }
6214
6215 if (mMinWidthMode == EMS) {
6216 width = Math.max(width, mMinWidth * getLineHeight());
6217 } else {
6218 width = Math.max(width, mMinWidth);
6219 }
6220
6221 // Check against our minimum width
6222 width = Math.max(width, getSuggestedMinimumWidth());
6223
6224 if (widthMode == MeasureSpec.AT_MOST) {
6225 width = Math.min(widthSize, width);
6226 }
6227 }
6228
6229 int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
6230 int unpaddedWidth = want;
Gilles Debunne9a80a652011-01-31 12:56:07 -08006231
Jeff Brown033a0012011-11-11 15:30:16 -08006232 if (mHorizontallyScrolling) want = VERY_WIDE;
Gilles Debunne9a80a652011-01-31 12:56:07 -08006233
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006234 int hintWant = want;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07006235 int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006236
6237 if (mLayout == null) {
6238 makeNewLayout(want, hintWant, boring, hintBoring,
Romain Guy4dc4f732009-06-19 15:16:40 -07006239 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006240 } else {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07006241 final boolean layoutChanged = (mLayout.getWidth() != want) ||
6242 (hintWidth != hintWant) ||
6243 (mLayout.getEllipsizedWidth() !=
6244 width - getCompoundPaddingLeft() - getCompoundPaddingRight());
6245
6246 final boolean widthChanged = (mHint == null) &&
6247 (mEllipsize == null) &&
6248 (want > mLayout.getWidth()) &&
6249 (mLayout instanceof BoringLayout || (fromexisting && des >= 0 && des <= want));
6250
6251 final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
6252
6253 if (layoutChanged || maximumChanged) {
6254 if (!maximumChanged && widthChanged) {
6255 mLayout.increaseWidthTo(want);
6256 } else {
6257 makeNewLayout(want, hintWant, boring, hintBoring,
6258 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
6259 }
6260 } else {
6261 // Nothing has changed
6262 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006263 }
6264
6265 if (heightMode == MeasureSpec.EXACTLY) {
6266 // Parent has told us how big to be. So be it.
6267 height = heightSize;
6268 mDesiredHeightAtMeasure = -1;
6269 } else {
6270 int desired = getDesiredHeight();
6271
6272 height = desired;
6273 mDesiredHeightAtMeasure = desired;
6274
6275 if (heightMode == MeasureSpec.AT_MOST) {
Christoffer Gurell1d05c7c2009-10-12 15:53:39 +02006276 height = Math.min(desired, heightSize);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006277 }
6278 }
6279
Romain Guy4dc4f732009-06-19 15:16:40 -07006280 int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006281 if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
Romain Guy4dc4f732009-06-19 15:16:40 -07006282 unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006283 }
6284
6285 /*
6286 * We didn't let makeNewLayout() register to bring the cursor into view,
6287 * so do it here if there is any possibility that it is needed.
6288 */
6289 if (mMovement != null ||
6290 mLayout.getWidth() > unpaddedWidth ||
6291 mLayout.getHeight() > unpaddedHeight) {
6292 registerForPreDraw();
6293 } else {
6294 scrollTo(0, 0);
6295 }
6296
6297 setMeasuredDimension(width, height);
6298 }
6299
6300 private int getDesiredHeight() {
Romain Guy4dc4f732009-06-19 15:16:40 -07006301 return Math.max(
6302 getDesiredHeight(mLayout, true),
6303 getDesiredHeight(mHintLayout, mEllipsize != null));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006304 }
6305
6306 private int getDesiredHeight(Layout layout, boolean cap) {
6307 if (layout == null) {
6308 return 0;
6309 }
6310
6311 int linecount = layout.getLineCount();
6312 int pad = getCompoundPaddingTop() + getCompoundPaddingBottom();
6313 int desired = layout.getLineTop(linecount);
6314
6315 final Drawables dr = mDrawables;
6316 if (dr != null) {
6317 desired = Math.max(desired, dr.mDrawableHeightLeft);
6318 desired = Math.max(desired, dr.mDrawableHeightRight);
6319 }
6320
6321 desired += pad;
6322
6323 if (mMaxMode == LINES) {
6324 /*
6325 * Don't cap the hint to a certain number of lines.
6326 * (Do cap it, though, if we have a maximum pixel height.)
6327 */
6328 if (cap) {
6329 if (linecount > mMaximum) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -08006330 desired = layout.getLineTop(mMaximum);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006331
6332 if (dr != null) {
6333 desired = Math.max(desired, dr.mDrawableHeightLeft);
6334 desired = Math.max(desired, dr.mDrawableHeightRight);
6335 }
6336
6337 desired += pad;
6338 linecount = mMaximum;
6339 }
6340 }
6341 } else {
6342 desired = Math.min(desired, mMaximum);
6343 }
6344
6345 if (mMinMode == LINES) {
6346 if (linecount < mMinimum) {
6347 desired += getLineHeight() * (mMinimum - linecount);
6348 }
6349 } else {
6350 desired = Math.max(desired, mMinimum);
6351 }
6352
6353 // Check against our minimum height
6354 desired = Math.max(desired, getSuggestedMinimumHeight());
6355
6356 return desired;
6357 }
6358
6359 /**
6360 * Check whether a change to the existing text layout requires a
6361 * new view layout.
6362 */
6363 private void checkForResize() {
6364 boolean sizeChanged = false;
6365
6366 if (mLayout != null) {
6367 // Check if our width changed
6368 if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
6369 sizeChanged = true;
6370 invalidate();
6371 }
6372
6373 // Check if our height changed
6374 if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
6375 int desiredHeight = getDesiredHeight();
6376
6377 if (desiredHeight != this.getHeight()) {
6378 sizeChanged = true;
6379 }
Romain Guy980a9382010-01-08 15:06:28 -08006380 } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006381 if (mDesiredHeightAtMeasure >= 0) {
6382 int desiredHeight = getDesiredHeight();
6383
6384 if (desiredHeight != mDesiredHeightAtMeasure) {
6385 sizeChanged = true;
6386 }
6387 }
6388 }
6389 }
6390
6391 if (sizeChanged) {
6392 requestLayout();
6393 // caller will have already invalidated
6394 }
6395 }
6396
6397 /**
6398 * Check whether entirely new text requires a new view layout
6399 * or merely a new text layout.
6400 */
6401 private void checkForRelayout() {
6402 // If we have a fixed width, we can just swap in a new text layout
6403 // if the text height stays the same or if the view height is fixed.
6404
6405 if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||
6406 (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&
6407 (mHint == null || mHintLayout != null) &&
6408 (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
6409 // Static width, so try making a new text layout.
6410
6411 int oldht = mLayout.getHeight();
6412 int want = mLayout.getWidth();
6413 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
6414
6415 /*
6416 * No need to bring the text into view, since the size is not
6417 * changing (unless we do the requestLayout(), in which case it
6418 * will happen at measure).
6419 */
6420 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
Romain Guye1e0dc82009-11-03 17:21:04 -08006421 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
6422 false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006423
Romain Guye1e0dc82009-11-03 17:21:04 -08006424 if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
6425 // In a fixed-height view, so use our new text layout.
6426 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
Romain Guy980a9382010-01-08 15:06:28 -08006427 mLayoutParams.height != LayoutParams.MATCH_PARENT) {
Romain Guye1e0dc82009-11-03 17:21:04 -08006428 invalidate();
6429 return;
6430 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006431
Romain Guye1e0dc82009-11-03 17:21:04 -08006432 // Dynamic height, but height has stayed the same,
6433 // so use our new text layout.
6434 if (mLayout.getHeight() == oldht &&
6435 (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
6436 invalidate();
6437 return;
6438 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006439 }
6440
6441 // We lose: the height has changed and we have a dynamic height.
6442 // Request a new view layout using our new text layout.
6443 requestLayout();
6444 invalidate();
6445 } else {
6446 // Dynamic width, so we have no choice but to request a new
6447 // view layout with a new text layout.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006448 nullLayouts();
6449 requestLayout();
6450 invalidate();
6451 }
6452 }
6453
Gilles Debunne954325e2012-01-25 11:57:06 -08006454 @Override
6455 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
6456 super.onLayout(changed, left, top, right, bottom);
Raph Levienf5c1a872012-10-15 17:22:26 -07006457 if (mDeferScroll >= 0) {
6458 int curs = mDeferScroll;
6459 mDeferScroll = -1;
Raph Levien8b179692012-10-16 14:32:47 -07006460 bringPointIntoView(Math.min(curs, mText.length()));
Raph Levienf5c1a872012-10-15 17:22:26 -07006461 }
Gilles Debunne954325e2012-01-25 11:57:06 -08006462 }
6463
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006464 private boolean isShowingHint() {
6465 return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint);
6466 }
6467
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006468 /**
6469 * Returns true if anything changed.
6470 */
6471 private boolean bringTextIntoView() {
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006472 Layout layout = isShowingHint() ? mHintLayout : mLayout;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006473 int line = 0;
6474 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006475 line = layout.getLineCount() - 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006476 }
6477
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006478 Layout.Alignment a = layout.getParagraphAlignment(line);
6479 int dir = layout.getParagraphDirection(line);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006480 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6481 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006482 int ht = layout.getHeight();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006483
6484 int scrollx, scrolly;
6485
Doug Felt25b9f422011-07-11 13:48:37 -07006486 // Convert to left, center, or right alignment.
6487 if (a == Layout.Alignment.ALIGN_NORMAL) {
6488 a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_LEFT :
6489 Layout.Alignment.ALIGN_RIGHT;
6490 } else if (a == Layout.Alignment.ALIGN_OPPOSITE){
6491 a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_RIGHT :
6492 Layout.Alignment.ALIGN_LEFT;
6493 }
6494
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006495 if (a == Layout.Alignment.ALIGN_CENTER) {
6496 /*
6497 * Keep centered if possible, or, if it is too wide to fit,
6498 * keep leading edge in view.
6499 */
6500
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006501 int left = (int) FloatMath.floor(layout.getLineLeft(line));
6502 int right = (int) FloatMath.ceil(layout.getLineRight(line));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006503
6504 if (right - left < hspace) {
6505 scrollx = (right + left) / 2 - hspace / 2;
6506 } else {
6507 if (dir < 0) {
6508 scrollx = right - hspace;
6509 } else {
6510 scrollx = left;
6511 }
6512 }
Fabrice Di Megliod2b5d1c2011-07-13 19:38:17 -07006513 } else if (a == Layout.Alignment.ALIGN_RIGHT) {
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006514 int right = (int) FloatMath.ceil(layout.getLineRight(line));
Doug Felt25b9f422011-07-11 13:48:37 -07006515 scrollx = right - hspace;
Fabrice Di Megliod2b5d1c2011-07-13 19:38:17 -07006516 } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006517 scrollx = (int) FloatMath.floor(layout.getLineLeft(line));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006518 }
6519
6520 if (ht < vspace) {
6521 scrolly = 0;
6522 } else {
6523 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
6524 scrolly = ht - vspace;
6525 } else {
6526 scrolly = 0;
6527 }
6528 }
6529
6530 if (scrollx != mScrollX || scrolly != mScrollY) {
6531 scrollTo(scrollx, scrolly);
6532 return true;
6533 } else {
6534 return false;
6535 }
6536 }
6537
6538 /**
6539 * Move the point, specified by the offset, into the view if it is needed.
6540 * This has to be called after layout. Returns true if anything changed.
6541 */
6542 public boolean bringPointIntoView(int offset) {
Raph Levienf5c1a872012-10-15 17:22:26 -07006543 if (isLayoutRequested()) {
6544 mDeferScroll = offset;
6545 return false;
6546 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006547 boolean changed = false;
6548
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006549 Layout layout = isShowingHint() ? mHintLayout: mLayout;
Gilles Debunne176ee3d2011-07-16 13:28:41 -07006550
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006551 if (layout == null) return changed;
6552
6553 int line = layout.getLineForOffset(offset);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006554
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006555 int grav;
6556
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006557 switch (layout.getParagraphAlignment(line)) {
Doug Felt25b9f422011-07-11 13:48:37 -07006558 case ALIGN_LEFT:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006559 grav = 1;
6560 break;
Doug Felt25b9f422011-07-11 13:48:37 -07006561 case ALIGN_RIGHT:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006562 grav = -1;
6563 break;
Doug Felt25b9f422011-07-11 13:48:37 -07006564 case ALIGN_NORMAL:
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006565 grav = layout.getParagraphDirection(line);
Doug Felt25b9f422011-07-11 13:48:37 -07006566 break;
6567 case ALIGN_OPPOSITE:
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006568 grav = -layout.getParagraphDirection(line);
Doug Felt25b9f422011-07-11 13:48:37 -07006569 break;
6570 case ALIGN_CENTER:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006571 default:
6572 grav = 0;
Doug Felt25b9f422011-07-11 13:48:37 -07006573 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006574 }
6575
Raph Levienafe8e9b2012-12-19 16:09:32 -08006576 // We only want to clamp the cursor to fit within the layout width
6577 // in left-to-right modes, because in a right to left alignment,
6578 // we want to scroll to keep the line-right on the screen, as other
6579 // lines are likely to have text flush with the right margin, which
6580 // we want to keep visible.
6581 // A better long-term solution would probably be to measure both
6582 // the full line and a blank-trimmed version, and, for example, use
6583 // the latter measurement for centering and right alignment, but for
6584 // the time being we only implement the cursor clamping in left to
6585 // right where it is most likely to be annoying.
6586 final boolean clamped = grav > 0;
6587 // FIXME: Is it okay to truncate this, or should we round?
6588 final int x = (int)layout.getPrimaryHorizontal(offset, clamped);
6589 final int top = layout.getLineTop(line);
6590 final int bottom = layout.getLineTop(line + 1);
6591
6592 int left = (int) FloatMath.floor(layout.getLineLeft(line));
6593 int right = (int) FloatMath.ceil(layout.getLineRight(line));
6594 int ht = layout.getHeight();
6595
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006596 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6597 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
Raph Levienafe8e9b2012-12-19 16:09:32 -08006598 if (!mHorizontallyScrolling && right - left > hspace && right > x) {
6599 // If cursor has been clamped, make sure we don't scroll.
6600 right = Math.max(x, left + hspace);
6601 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006602
6603 int hslack = (bottom - top) / 2;
6604 int vslack = hslack;
6605
6606 if (vslack > vspace / 4)
6607 vslack = vspace / 4;
6608 if (hslack > hspace / 4)
6609 hslack = hspace / 4;
6610
6611 int hs = mScrollX;
6612 int vs = mScrollY;
6613
6614 if (top - vs < vslack)
6615 vs = top - vslack;
6616 if (bottom - vs > vspace - vslack)
6617 vs = bottom - (vspace - vslack);
6618 if (ht - vs < vspace)
6619 vs = ht - vspace;
6620 if (0 - vs > 0)
6621 vs = 0;
6622
6623 if (grav != 0) {
6624 if (x - hs < hslack) {
6625 hs = x - hslack;
6626 }
6627 if (x - hs > hspace - hslack) {
6628 hs = x - (hspace - hslack);
6629 }
6630 }
6631
6632 if (grav < 0) {
6633 if (left - hs > 0)
6634 hs = left;
6635 if (right - hs < hspace)
6636 hs = right - hspace;
6637 } else if (grav > 0) {
6638 if (right - hs < hspace)
6639 hs = right - hspace;
6640 if (left - hs > 0)
6641 hs = left;
6642 } else /* grav == 0 */ {
6643 if (right - left <= hspace) {
6644 /*
6645 * If the entire text fits, center it exactly.
6646 */
6647 hs = left - (hspace - (right - left)) / 2;
6648 } else if (x > right - hslack) {
6649 /*
6650 * If we are near the right edge, keep the right edge
6651 * at the edge of the view.
6652 */
6653 hs = right - hspace;
6654 } else if (x < left + hslack) {
6655 /*
6656 * If we are near the left edge, keep the left edge
6657 * at the edge of the view.
6658 */
6659 hs = left;
6660 } else if (left > hs) {
6661 /*
6662 * Is there whitespace visible at the left? Fix it if so.
6663 */
6664 hs = left;
6665 } else if (right < hs + hspace) {
6666 /*
6667 * Is there whitespace visible at the right? Fix it if so.
6668 */
6669 hs = right - hspace;
6670 } else {
6671 /*
6672 * Otherwise, float as needed.
6673 */
6674 if (x - hs < hslack) {
6675 hs = x - hslack;
6676 }
6677 if (x - hs > hspace - hslack) {
6678 hs = x - (hspace - hslack);
6679 }
6680 }
6681 }
6682
6683 if (hs != mScrollX || vs != mScrollY) {
6684 if (mScroller == null) {
6685 scrollTo(hs, vs);
6686 } else {
6687 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
6688 int dx = hs - mScrollX;
6689 int dy = vs - mScrollY;
6690
6691 if (duration > ANIMATED_SCROLL_GAP) {
6692 mScroller.startScroll(mScrollX, mScrollY, dx, dy);
Mike Cleronf116bf82009-09-27 19:14:12 -07006693 awakenScrollBars(mScroller.getDuration());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006694 invalidate();
6695 } else {
6696 if (!mScroller.isFinished()) {
6697 mScroller.abortAnimation();
6698 }
6699
6700 scrollBy(dx, dy);
6701 }
6702
6703 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
6704 }
6705
6706 changed = true;
6707 }
6708
6709 if (isFocused()) {
Gilles Debunne716dbf62011-03-07 18:12:10 -08006710 // This offsets because getInterestingRect() is in terms of viewport coordinates, but
6711 // requestRectangleOnScreen() is in terms of content coordinates.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006712
Dianne Hackborn70a3f672011-08-08 14:32:41 -07006713 // The offsets here are to ensure the rectangle we are using is
6714 // within our view bounds, in case the cursor is on the far left
6715 // or right. If it isn't withing the bounds, then this request
6716 // will be ignored.
Gilles Debunne60e21862012-01-30 15:04:14 -08006717 if (mTempRect == null) mTempRect = new Rect();
Dianne Hackborn70a3f672011-08-08 14:32:41 -07006718 mTempRect.set(x - 2, top, x + 2, bottom);
Gilles Debunne716dbf62011-03-07 18:12:10 -08006719 getInterestingRect(mTempRect, line);
6720 mTempRect.offset(mScrollX, mScrollY);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006721
Gilles Debunne716dbf62011-03-07 18:12:10 -08006722 if (requestRectangleOnScreen(mTempRect)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006723 changed = true;
6724 }
6725 }
6726
6727 return changed;
6728 }
6729
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006730 /**
6731 * Move the cursor, if needed, so that it is at an offset that is visible
6732 * to the user. This will not move the cursor if it represents more than
6733 * one character (a selection range). This will only work if the
6734 * TextView contains spannable text; otherwise it will do nothing.
Gilles Debunnebbb5d6e2010-06-21 16:21:51 -07006735 *
Gilles Debunne57f4e5b2010-06-21 16:21:51 -07006736 * @return True if the cursor was actually moved, false otherwise.
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006737 */
6738 public boolean moveCursorToVisibleOffset() {
6739 if (!(mText instanceof Spannable)) {
6740 return false;
6741 }
Gilles Debunne05336272010-07-09 20:13:45 -07006742 int start = getSelectionStart();
6743 int end = getSelectionEnd();
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006744 if (start != end) {
6745 return false;
6746 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006747
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006748 // First: make sure the line is visible on screen:
Gilles Debunne2d373a12012-04-20 15:32:19 -07006749
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006750 int line = mLayout.getLineForOffset(start);
6751
6752 final int top = mLayout.getLineTop(line);
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07006753 final int bottom = mLayout.getLineTop(line + 1);
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006754 final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
6755 int vslack = (bottom - top) / 2;
6756 if (vslack > vspace / 4)
6757 vslack = vspace / 4;
6758 final int vs = mScrollY;
6759
6760 if (top < (vs+vslack)) {
6761 line = mLayout.getLineForVertical(vs+vslack+(bottom-top));
6762 } else if (bottom > (vspace+vs-vslack)) {
6763 line = mLayout.getLineForVertical(vspace+vs-vslack-(bottom-top));
6764 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006765
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006766 // Next: make sure the character is visible on screen:
Gilles Debunne2d373a12012-04-20 15:32:19 -07006767
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006768 final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6769 final int hs = mScrollX;
6770 final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
6771 final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs);
Gilles Debunne2d373a12012-04-20 15:32:19 -07006772
Doug Feltc982f602010-05-25 11:51:40 -07006773 // line might contain bidirectional text
6774 final int lowChar = leftChar < rightChar ? leftChar : rightChar;
6775 final int highChar = leftChar > rightChar ? leftChar : rightChar;
6776
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006777 int newStart = start;
Doug Feltc982f602010-05-25 11:51:40 -07006778 if (newStart < lowChar) {
6779 newStart = lowChar;
6780 } else if (newStart > highChar) {
6781 newStart = highChar;
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006782 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006783
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006784 if (newStart != start) {
6785 Selection.setSelection((Spannable)mText, newStart);
6786 return true;
6787 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006788
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006789 return false;
6790 }
6791
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006792 @Override
6793 public void computeScroll() {
6794 if (mScroller != null) {
6795 if (mScroller.computeScrollOffset()) {
6796 mScrollX = mScroller.getCurrX();
6797 mScrollY = mScroller.getCurrY();
Romain Guy0fd89bf2011-01-26 15:41:30 -08006798 invalidateParentCaches();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006799 postInvalidate(); // So we draw again
6800 }
6801 }
6802 }
6803
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07006804 private void getInterestingRect(Rect r, int line) {
6805 convertFromViewportToContentCoordinates(r);
6806
6807 // Rectangle can can be expanded on first and last line to take
6808 // padding into account.
6809 // TODO Take left/right padding into account too?
6810 if (line == 0) r.top -= getExtendedPaddingTop();
6811 if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
6812 }
6813
6814 private void convertFromViewportToContentCoordinates(Rect r) {
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07006815 final int horizontalOffset = viewportToContentHorizontalOffset();
6816 r.left += horizontalOffset;
6817 r.right += horizontalOffset;
6818
6819 final int verticalOffset = viewportToContentVerticalOffset();
6820 r.top += verticalOffset;
6821 r.bottom += verticalOffset;
6822 }
6823
Gilles Debunned88876a2012-03-16 17:34:04 -07006824 int viewportToContentHorizontalOffset() {
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07006825 return getCompoundPaddingLeft() - mScrollX;
6826 }
6827
Gilles Debunned88876a2012-03-16 17:34:04 -07006828 int viewportToContentVerticalOffset() {
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07006829 int offset = getExtendedPaddingTop() - mScrollY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006830 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07006831 offset += getVerticalOffset(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006832 }
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07006833 return offset;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006834 }
6835
6836 @Override
6837 public void debug(int depth) {
6838 super.debug(depth);
6839
6840 String output = debugIndent(depth);
6841 output += "frame={" + mLeft + ", " + mTop + ", " + mRight
6842 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
6843 + "} ";
6844
6845 if (mText != null) {
6846
6847 output += "mText=\"" + mText + "\" ";
6848 if (mLayout != null) {
6849 output += "mLayout width=" + mLayout.getWidth()
6850 + " height=" + mLayout.getHeight();
6851 }
6852 } else {
6853 output += "mText=NULL";
6854 }
6855 Log.d(VIEW_LOG_TAG, output);
6856 }
6857
6858 /**
6859 * Convenience for {@link Selection#getSelectionStart}.
6860 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006861 @ViewDebug.ExportedProperty(category = "text")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006862 public int getSelectionStart() {
6863 return Selection.getSelectionStart(getText());
6864 }
6865
6866 /**
6867 * Convenience for {@link Selection#getSelectionEnd}.
6868 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006869 @ViewDebug.ExportedProperty(category = "text")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006870 public int getSelectionEnd() {
6871 return Selection.getSelectionEnd(getText());
6872 }
6873
6874 /**
6875 * Return true iff there is a selection inside this text view.
6876 */
6877 public boolean hasSelection() {
Gilles Debunne03789e82010-09-07 19:07:17 -07006878 final int selectionStart = getSelectionStart();
6879 final int selectionEnd = getSelectionEnd();
6880
6881 return selectionStart >= 0 && selectionStart != selectionEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006882 }
6883
6884 /**
6885 * Sets the properties of this field (lines, horizontally scrolling,
6886 * transformation method) to be for a single-line input.
6887 *
6888 * @attr ref android.R.styleable#TextView_singleLine
6889 */
6890 public void setSingleLine() {
6891 setSingleLine(true);
6892 }
6893
6894 /**
Adam Powell7f8f79a2011-07-07 18:35:54 -07006895 * Sets the properties of this field to transform input to ALL CAPS
6896 * display. This may use a "small caps" formatting if available.
6897 * This setting will be ignored if this field is editable or selectable.
6898 *
6899 * This call replaces the current transformation method. Disabling this
6900 * will not necessarily restore the previous behavior from before this
6901 * was enabled.
6902 *
6903 * @see #setTransformationMethod(TransformationMethod)
6904 * @attr ref android.R.styleable#TextView_textAllCaps
6905 */
6906 public void setAllCaps(boolean allCaps) {
6907 if (allCaps) {
6908 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
6909 } else {
6910 setTransformationMethod(null);
6911 }
6912 }
6913
6914 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08006915 * If true, sets the properties of this field (number of lines, horizontally scrolling,
6916 * transformation method) to be for a single-line input; if false, restores these to the default
6917 * conditions.
6918 *
6919 * Note that the default conditions are not necessarily those that were in effect prior this
6920 * method, and you may want to reset these properties to your custom values.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006921 *
6922 * @attr ref android.R.styleable#TextView_singleLine
6923 */
6924 @android.view.RemotableViewMethod
6925 public void setSingleLine(boolean singleLine) {
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08006926 // Could be used, but may break backward compatibility.
6927 // if (mSingleLine == singleLine) return;
Gilles Debunned7483bf2010-11-10 10:47:45 -08006928 setInputTypeSingleLine(singleLine);
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08006929 applySingleLine(singleLine, true, true);
Gilles Debunned7483bf2010-11-10 10:47:45 -08006930 }
6931
6932 /**
6933 * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
6934 * @param singleLine
6935 */
6936 private void setInputTypeSingleLine(boolean singleLine) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07006937 if (mEditor != null &&
6938 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006939 if (singleLine) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07006940 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006941 } else {
Gilles Debunne2d373a12012-04-20 15:32:19 -07006942 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006943 }
6944 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006945 }
6946
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08006947 private void applySingleLine(boolean singleLine, boolean applyTransformation,
6948 boolean changeMaxLines) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006949 mSingleLine = singleLine;
6950 if (singleLine) {
6951 setLines(1);
6952 setHorizontallyScrolling(true);
6953 if (applyTransformation) {
Gilles Debunne91a08cf2010-11-08 17:34:49 -08006954 setTransformationMethod(SingleLineTransformationMethod.getInstance());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006955 }
6956 } else {
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08006957 if (changeMaxLines) {
6958 setMaxLines(Integer.MAX_VALUE);
6959 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006960 setHorizontallyScrolling(false);
6961 if (applyTransformation) {
6962 setTransformationMethod(null);
6963 }
6964 }
6965 }
Gilles Debunneb2316962010-12-21 17:32:43 -08006966
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006967 /**
6968 * Causes words in the text that are longer than the view is wide
6969 * to be ellipsized instead of broken in the middle. You may also
6970 * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
Kenny Roote855d132009-06-11 11:00:42 -05006971 * to constrain the text to a single line. Use <code>null</code>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006972 * to turn off ellipsizing.
6973 *
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07006974 * If {@link #setMaxLines} has been used to set two or more lines,
Gilles Debunne6435a562011-08-04 21:22:30 -07006975 * {@link android.text.TextUtils.TruncateAt#END} and
6976 * {@link android.text.TextUtils.TruncateAt#MARQUEE}* are only supported
6977 * (other ellipsizing types will not do anything).
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07006978 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006979 * @attr ref android.R.styleable#TextView_ellipsize
6980 */
6981 public void setEllipsize(TextUtils.TruncateAt where) {
Gilles Debunne22378292011-08-12 10:38:52 -07006982 // TruncateAt is an enum. != comparison is ok between these singleton objects.
6983 if (mEllipsize != where) {
6984 mEllipsize = where;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006985
Gilles Debunne22378292011-08-12 10:38:52 -07006986 if (mLayout != null) {
6987 nullLayouts();
6988 requestLayout();
6989 invalidate();
6990 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006991 }
6992 }
6993
6994 /**
6995 * Sets how many times to repeat the marquee animation. Only applied if the
6996 * TextView has marquee enabled. Set to -1 to repeat indefinitely.
6997 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07006998 * @see #getMarqueeRepeatLimit()
6999 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007000 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
7001 */
7002 public void setMarqueeRepeatLimit(int marqueeLimit) {
7003 mMarqueeRepeatLimit = marqueeLimit;
7004 }
7005
7006 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07007007 * Gets the number of times the marquee animation is repeated. Only meaningful if the
7008 * TextView has marquee enabled.
7009 *
7010 * @return the number of times the marquee animation is repeated. -1 if the animation
7011 * repeats indefinitely
7012 *
7013 * @see #setMarqueeRepeatLimit(int)
7014 *
7015 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
7016 */
7017 public int getMarqueeRepeatLimit() {
7018 return mMarqueeRepeatLimit;
7019 }
7020
7021 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007022 * Returns where, if anywhere, words that are longer than the view
7023 * is wide should be ellipsized.
7024 */
7025 @ViewDebug.ExportedProperty
7026 public TextUtils.TruncateAt getEllipsize() {
7027 return mEllipsize;
7028 }
7029
7030 /**
7031 * Set the TextView so that when it takes focus, all the text is
7032 * selected.
7033 *
7034 * @attr ref android.R.styleable#TextView_selectAllOnFocus
7035 */
7036 @android.view.RemotableViewMethod
7037 public void setSelectAllOnFocus(boolean selectAllOnFocus) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07007038 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07007039 mEditor.mSelectAllOnFocus = selectAllOnFocus;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007040
7041 if (selectAllOnFocus && !(mText instanceof Spannable)) {
7042 setText(mText, BufferType.SPANNABLE);
7043 }
7044 }
7045
7046 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07007047 * Set whether the cursor is visible. The default is true. Note that this property only
7048 * makes sense for editable TextView.
7049 *
7050 * @see #isCursorVisible()
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007051 *
7052 * @attr ref android.R.styleable#TextView_cursorVisible
7053 */
7054 @android.view.RemotableViewMethod
7055 public void setCursorVisible(boolean visible) {
Gilles Debunne60e21862012-01-30 15:04:14 -08007056 if (visible && mEditor == null) return; // visible is the default value with no edit data
Gilles Debunne5fae9962012-05-08 14:53:20 -07007057 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07007058 if (mEditor.mCursorVisible != visible) {
7059 mEditor.mCursorVisible = visible;
Gilles Debunne3d010062011-02-18 14:16:41 -08007060 invalidate();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007061
Gilles Debunne2d373a12012-04-20 15:32:19 -07007062 mEditor.makeBlink();
Gilles Debunnef788a9f2010-07-22 10:17:23 -07007063
Gilles Debunne3d010062011-02-18 14:16:41 -08007064 // InsertionPointCursorController depends on mCursorVisible
Gilles Debunne2d373a12012-04-20 15:32:19 -07007065 mEditor.prepareCursorControllers();
Gilles Debunne3d010062011-02-18 14:16:41 -08007066 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007067 }
7068
Gilles Debunnef03acef2012-04-30 19:26:19 -07007069 /**
7070 * @return whether or not the cursor is visible (assuming this TextView is editable)
7071 *
7072 * @see #setCursorVisible(boolean)
7073 *
7074 * @attr ref android.R.styleable#TextView_cursorVisible
7075 */
7076 public boolean isCursorVisible() {
7077 // true is the default value
7078 return mEditor == null ? true : mEditor.mCursorVisible;
7079 }
7080
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007081 private boolean canMarquee() {
7082 int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight());
Adam Powell282e3772011-08-30 16:51:11 -07007083 return width > 0 && (mLayout.getLineWidth(0) > width ||
7084 (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null &&
7085 mSavedMarqueeModeLayout.getLineWidth(0) > width));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007086 }
7087
7088 private void startMarquee() {
Romain Guy4dc4f732009-06-19 15:16:40 -07007089 // Do not ellipsize EditText
Gilles Debunne60e21862012-01-30 15:04:14 -08007090 if (getKeyListener() != null) return;
Romain Guy4dc4f732009-06-19 15:16:40 -07007091
Romain Guy939151f2009-04-08 14:22:40 -07007092 if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
7093 return;
7094 }
7095
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007096 if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) &&
7097 getLineCount() == 1 && canMarquee()) {
Romain Guy939151f2009-04-08 14:22:40 -07007098
Adam Powell282e3772011-08-30 16:51:11 -07007099 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
7100 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
7101 final Layout tmp = mLayout;
7102 mLayout = mSavedMarqueeModeLayout;
7103 mSavedMarqueeModeLayout = tmp;
7104 setHorizontalFadingEdgeEnabled(true);
7105 requestLayout();
7106 invalidate();
7107 }
7108
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007109 if (mMarquee == null) mMarquee = new Marquee(this);
7110 mMarquee.start(mMarqueeRepeatLimit);
7111 }
7112 }
7113
7114 private void stopMarquee() {
7115 if (mMarquee != null && !mMarquee.isStopped()) {
7116 mMarquee.stop();
7117 }
Adam Powell282e3772011-08-30 16:51:11 -07007118
7119 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
7120 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
7121 final Layout tmp = mSavedMarqueeModeLayout;
7122 mSavedMarqueeModeLayout = mLayout;
7123 mLayout = tmp;
7124 setHorizontalFadingEdgeEnabled(false);
7125 requestLayout();
7126 invalidate();
7127 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007128 }
7129
7130 private void startStopMarquee(boolean start) {
7131 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7132 if (start) {
7133 startMarquee();
7134 } else {
7135 stopMarquee();
7136 }
7137 }
7138 }
7139
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007140 /**
Gilles Debunne4469e602011-03-09 14:38:04 -08007141 * This method is called when the text is changed, in case any subclasses
7142 * would like to know.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007143 *
Gilles Debunne4469e602011-03-09 14:38:04 -08007144 * Within <code>text</code>, the <code>lengthAfter</code> characters
7145 * beginning at <code>start</code> have just replaced old text that had
7146 * length <code>lengthBefore</code>. It is an error to attempt to make
7147 * changes to <code>text</code> from this callback.
7148 *
7149 * @param text The text the TextView is displaying
7150 * @param start The offset of the start of the range of the text that was
7151 * modified
7152 * @param lengthBefore The length of the former text that has been replaced
7153 * @param lengthAfter The length of the replacement modified text
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007154 */
Gilles Debunne4469e602011-03-09 14:38:04 -08007155 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
Gilles Debunne6435a562011-08-04 21:22:30 -07007156 // intentionally empty, template pattern method can be overridden by subclasses
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007157 }
7158
7159 /**
7160 * This method is called when the selection has changed, in case any
7161 * subclasses would like to know.
Gilles Debunne2d373a12012-04-20 15:32:19 -07007162 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007163 * @param selStart The new selection start location.
7164 * @param selEnd The new selection end location.
7165 */
7166 protected void onSelectionChanged(int selStart, int selEnd) {
Svetoslav Ganova0156172011-06-26 17:55:44 -07007167 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
Svetoslavbcc46a02013-02-06 11:56:00 -08007168 notifyAccessibilityStateChanged();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007169 }
Svetoslav Ganova0156172011-06-26 17:55:44 -07007170
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007171 /**
7172 * Adds a TextWatcher to the list of those whose methods are called
7173 * whenever this TextView's text changes.
7174 * <p>
7175 * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
7176 * not called after {@link #setText} calls. Now, doing {@link #setText}
7177 * if there are any text changed listeners forces the buffer type to
7178 * Editable if it would not otherwise be and does call this method.
7179 */
7180 public void addTextChangedListener(TextWatcher watcher) {
7181 if (mListeners == null) {
7182 mListeners = new ArrayList<TextWatcher>();
7183 }
7184
7185 mListeners.add(watcher);
7186 }
7187
7188 /**
7189 * Removes the specified TextWatcher from the list of those whose
7190 * methods are called
7191 * whenever this TextView's text changes.
7192 */
7193 public void removeTextChangedListener(TextWatcher watcher) {
7194 if (mListeners != null) {
7195 int i = mListeners.indexOf(watcher);
7196
7197 if (i >= 0) {
7198 mListeners.remove(i);
7199 }
7200 }
7201 }
7202
Gilles Debunne6435a562011-08-04 21:22:30 -07007203 private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007204 if (mListeners != null) {
7205 final ArrayList<TextWatcher> list = mListeners;
7206 final int count = list.size();
7207 for (int i = 0; i < count; i++) {
7208 list.get(i).beforeTextChanged(text, start, before, after);
7209 }
7210 }
Gilles Debunne6435a562011-08-04 21:22:30 -07007211
7212 // The spans that are inside or intersect the modified region no longer make sense
7213 removeIntersectingSpans(start, start + before, SpellCheckSpan.class);
7214 removeIntersectingSpans(start, start + before, SuggestionSpan.class);
7215 }
7216
7217 // Removes all spans that are inside or actually overlap the start..end range
7218 private <T> void removeIntersectingSpans(int start, int end, Class<T> type) {
7219 if (!(mText instanceof Editable)) return;
7220 Editable text = (Editable) mText;
7221
7222 T[] spans = text.getSpans(start, end, type);
7223 final int length = spans.length;
7224 for (int i = 0; i < length; i++) {
7225 final int s = text.getSpanStart(spans[i]);
7226 final int e = text.getSpanEnd(spans[i]);
Gilles Debunneb062e812011-09-27 14:58:37 -07007227 // Spans that are adjacent to the edited region will be handled in
7228 // updateSpellCheckSpans. Result depends on what will be added (space or text)
Gilles Debunne6435a562011-08-04 21:22:30 -07007229 if (e == start || s == end) break;
7230 text.removeSpan(spans[i]);
7231 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007232 }
7233
7234 /**
7235 * Not private so it can be called from an inner class without going
7236 * through a thunk.
7237 */
Gilles Debunne6435a562011-08-04 21:22:30 -07007238 void sendOnTextChanged(CharSequence text, int start, int before, int after) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007239 if (mListeners != null) {
7240 final ArrayList<TextWatcher> list = mListeners;
7241 final int count = list.size();
7242 for (int i = 0; i < count; i++) {
7243 list.get(i).onTextChanged(text, start, before, after);
7244 }
7245 }
Gilles Debunne1a22db22011-11-20 22:13:21 +01007246
Gilles Debunne2d373a12012-04-20 15:32:19 -07007247 if (mEditor != null) mEditor.sendOnTextChanged(start, after);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007248 }
7249
7250 /**
7251 * Not private so it can be called from an inner class without going
7252 * through a thunk.
7253 */
7254 void sendAfterTextChanged(Editable text) {
7255 if (mListeners != null) {
7256 final ArrayList<TextWatcher> list = mListeners;
7257 final int count = list.size();
7258 for (int i = 0; i < count; i++) {
7259 list.get(i).afterTextChanged(text);
7260 }
7261 }
7262 }
7263
Gilles Debunned88876a2012-03-16 17:34:04 -07007264 void updateAfterEdit() {
7265 invalidate();
7266 int curs = getSelectionStart();
7267
7268 if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
7269 registerForPreDraw();
7270 }
7271
Raph Levienf5c1a872012-10-15 17:22:26 -07007272 checkForResize();
7273
Gilles Debunned88876a2012-03-16 17:34:04 -07007274 if (curs >= 0) {
7275 mHighlightPathBogus = true;
Gilles Debunne2d373a12012-04-20 15:32:19 -07007276 if (mEditor != null) mEditor.makeBlink();
Gilles Debunned88876a2012-03-16 17:34:04 -07007277 bringPointIntoView(curs);
7278 }
Gilles Debunned88876a2012-03-16 17:34:04 -07007279 }
7280
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007281 /**
7282 * Not private so it can be called from an inner class without going
7283 * through a thunk.
7284 */
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08007285 void handleTextChanged(CharSequence buffer, int start, int before, int after) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007286 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007287 if (ims == null || ims.mBatchEditNesting == 0) {
7288 updateAfterEdit();
7289 }
7290 if (ims != null) {
7291 ims.mContentChanged = true;
7292 if (ims.mChangedStart < 0) {
7293 ims.mChangedStart = start;
7294 ims.mChangedEnd = start+before;
7295 } else {
Viktor Yakovel964be412010-02-17 08:35:57 +01007296 ims.mChangedStart = Math.min(ims.mChangedStart, start);
7297 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007298 }
7299 ims.mChangedDelta += after-before;
7300 }
Gilles Debunne186aaf92011-09-16 14:26:12 -07007301
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007302 sendOnTextChanged(buffer, start, before, after);
7303 onTextChanged(buffer, start, before, after);
7304 }
Gilles Debunne60e21862012-01-30 15:04:14 -08007305
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007306 /**
7307 * Not private so it can be called from an inner class without going
7308 * through a thunk.
7309 */
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08007310 void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007311 // XXX Make the start and end move together if this ends up
7312 // spending too much time invalidating.
7313
7314 boolean selChanged = false;
7315 int newSelStart=-1, newSelEnd=-1;
Gilles Debunne60e21862012-01-30 15:04:14 -08007316
Gilles Debunne2d373a12012-04-20 15:32:19 -07007317 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
Gilles Debunne60e21862012-01-30 15:04:14 -08007318
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007319 if (what == Selection.SELECTION_END) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007320 selChanged = true;
7321 newSelEnd = newStart;
7322
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007323 if (oldStart >= 0 || newStart >= 0) {
7324 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
Raph Levienf5c1a872012-10-15 17:22:26 -07007325 checkForResize();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007326 registerForPreDraw();
Gilles Debunne2d373a12012-04-20 15:32:19 -07007327 if (mEditor != null) mEditor.makeBlink();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007328 }
7329 }
7330
7331 if (what == Selection.SELECTION_START) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007332 selChanged = true;
7333 newSelStart = newStart;
7334
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007335 if (oldStart >= 0 || newStart >= 0) {
7336 int end = Selection.getSelectionEnd(buf);
7337 invalidateCursor(end, oldStart, newStart);
7338 }
7339 }
7340
7341 if (selChanged) {
Gilles Debunne83051b82012-02-24 20:01:13 -08007342 mHighlightPathBogus = true;
Gilles Debunne2d373a12012-04-20 15:32:19 -07007343 if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
Gilles Debunne60e21862012-01-30 15:04:14 -08007344
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007345 if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) {
7346 if (newSelStart < 0) {
7347 newSelStart = Selection.getSelectionStart(buf);
7348 }
7349 if (newSelEnd < 0) {
7350 newSelEnd = Selection.getSelectionEnd(buf);
7351 }
7352 onSelectionChanged(newSelStart, newSelEnd);
7353 }
7354 }
Gilles Debunne8615ac92011-11-29 15:25:03 -08007355
Gilles Debunneb35ab7b2011-12-05 15:54:00 -08007356 if (what instanceof UpdateAppearance || what instanceof ParagraphStyle ||
7357 what instanceof CharacterStyle) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007358 if (ims == null || ims.mBatchEditNesting == 0) {
7359 invalidate();
Gilles Debunne83051b82012-02-24 20:01:13 -08007360 mHighlightPathBogus = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007361 checkForResize();
7362 } else {
7363 ims.mContentChanged = true;
7364 }
Gilles Debunneebc86af2012-04-20 15:10:47 -07007365 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007366 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
7367 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
Gilles Debunneebc86af2012-04-20 15:10:47 -07007368 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007369 }
7370
7371 if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
Gilles Debunne83051b82012-02-24 20:01:13 -08007372 mHighlightPathBogus = true;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07007373 if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
7374 ims.mSelectionModeChanged = true;
7375 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007376
7377 if (Selection.getSelectionStart(buf) >= 0) {
7378 if (ims == null || ims.mBatchEditNesting == 0) {
7379 invalidateCursor();
7380 } else {
7381 ims.mCursorChanged = true;
7382 }
7383 }
7384 }
Gilles Debunne6435a562011-08-04 21:22:30 -07007385
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007386 if (what instanceof ParcelableSpan) {
7387 // If this is a span that can be sent to a remote process,
7388 // the current extract editor would be interested in it.
Gilles Debunnec62589c2012-04-12 14:50:23 -07007389 if (ims != null && ims.mExtractedTextRequest != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007390 if (ims.mBatchEditNesting != 0) {
7391 if (oldStart >= 0) {
7392 if (ims.mChangedStart > oldStart) {
7393 ims.mChangedStart = oldStart;
7394 }
7395 if (ims.mChangedStart > oldEnd) {
7396 ims.mChangedStart = oldEnd;
7397 }
7398 }
7399 if (newStart >= 0) {
7400 if (ims.mChangedStart > newStart) {
7401 ims.mChangedStart = newStart;
7402 }
7403 if (ims.mChangedStart > newEnd) {
7404 ims.mChangedStart = newEnd;
7405 }
7406 }
7407 } else {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007408 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: "
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007409 + oldStart + "-" + oldEnd + ","
Gilles Debunnec62589c2012-04-12 14:50:23 -07007410 + newStart + "-" + newEnd + " " + what);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007411 ims.mContentChanged = true;
7412 }
7413 }
7414 }
Gilles Debunne6435a562011-08-04 21:22:30 -07007415
Gilles Debunne2d373a12012-04-20 15:32:19 -07007416 if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0 &&
7417 what instanceof SpellCheckSpan) {
Gilles Debunne69865bd2012-05-09 11:12:03 -07007418 mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
Gilles Debunne6435a562011-08-04 21:22:30 -07007419 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007420 }
7421
Gilles Debunne6435a562011-08-04 21:22:30 -07007422 /**
Romain Guydcc490f2010-02-24 17:59:35 -08007423 * @hide
7424 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007425 @Override
Romain Guya440b002010-02-24 15:57:54 -08007426 public void dispatchFinishTemporaryDetach() {
7427 mDispatchTemporaryDetach = true;
7428 super.dispatchFinishTemporaryDetach();
7429 mDispatchTemporaryDetach = false;
7430 }
7431
7432 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007433 public void onStartTemporaryDetach() {
Romain Guya440b002010-02-24 15:57:54 -08007434 super.onStartTemporaryDetach();
7435 // Only track when onStartTemporaryDetach() is called directly,
7436 // usually because this instance is an editable field in a list
7437 if (!mDispatchTemporaryDetach) mTemporaryDetach = true;
Gilles Debunne4b2274f2011-02-25 15:18:03 -08007438
Adam Powell057a5852012-05-11 10:28:38 -07007439 // Tell the editor that we are temporarily detached. It can use this to preserve
7440 // selection state as needed.
7441 if (mEditor != null) mEditor.mTemporaryDetach = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007442 }
Gilles Debunne3784a7f2011-07-15 13:49:38 -07007443
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007444 @Override
7445 public void onFinishTemporaryDetach() {
Romain Guya440b002010-02-24 15:57:54 -08007446 super.onFinishTemporaryDetach();
7447 // Only track when onStartTemporaryDetach() is called directly,
7448 // usually because this instance is an editable field in a list
7449 if (!mDispatchTemporaryDetach) mTemporaryDetach = false;
Adam Powell057a5852012-05-11 10:28:38 -07007450 if (mEditor != null) mEditor.mTemporaryDetach = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007451 }
Gilles Debunne3784a7f2011-07-15 13:49:38 -07007452
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007453 @Override
7454 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
7455 if (mTemporaryDetach) {
7456 // If we are temporarily in the detach state, then do nothing.
7457 super.onFocusChanged(focused, direction, previouslyFocusedRect);
7458 return;
7459 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007460
Gilles Debunne2d373a12012-04-20 15:32:19 -07007461 if (mEditor != null) mEditor.onFocusChanged(focused, direction);
Gilles Debunne03789e82010-09-07 19:07:17 -07007462
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007463 if (focused) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007464 if (mText instanceof Spannable) {
7465 Spannable sp = (Spannable) mText;
7466 MetaKeyKeyListener.resetMetaState(sp);
7467 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007468 }
7469
7470 startStopMarquee(focused);
7471
7472 if (mTransformation != null) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07007473 mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007474 }
7475
7476 super.onFocusChanged(focused, direction, previouslyFocusedRect);
7477 }
7478
7479 @Override
7480 public void onWindowFocusChanged(boolean hasWindowFocus) {
7481 super.onWindowFocusChanged(hasWindowFocus);
7482
Gilles Debunne2d373a12012-04-20 15:32:19 -07007483 if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007484
7485 startStopMarquee(hasWindowFocus);
7486 }
7487
Adam Powellba0a2c32010-09-28 17:41:23 -07007488 @Override
7489 protected void onVisibilityChanged(View changedView, int visibility) {
7490 super.onVisibilityChanged(changedView, visibility);
Gilles Debunne60e21862012-01-30 15:04:14 -08007491 if (mEditor != null && visibility != VISIBLE) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007492 mEditor.hideControllers();
Adam Powellba0a2c32010-09-28 17:41:23 -07007493 }
7494 }
7495
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007496 /**
7497 * Use {@link BaseInputConnection#removeComposingSpans
7498 * BaseInputConnection.removeComposingSpans()} to remove any IME composing
7499 * state from this text view.
7500 */
7501 public void clearComposingText() {
7502 if (mText instanceof Spannable) {
7503 BaseInputConnection.removeComposingSpans((Spannable)mText);
7504 }
7505 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07007506
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007507 @Override
7508 public void setSelected(boolean selected) {
7509 boolean wasSelected = isSelected();
7510
7511 super.setSelected(selected);
7512
7513 if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7514 if (selected) {
7515 startMarquee();
7516 } else {
7517 stopMarquee();
7518 }
7519 }
7520 }
7521
7522 @Override
7523 public boolean onTouchEvent(MotionEvent event) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07007524 final int action = event.getActionMasked();
Adam Powell965b9692010-10-21 18:44:32 -07007525
Gilles Debunne2d373a12012-04-20 15:32:19 -07007526 if (mEditor != null) mEditor.onTouchEvent(event);
Gilles Debunnebbb5d6e2010-06-21 16:21:51 -07007527
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007528 final boolean superResult = super.onTouchEvent(event);
7529
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007530 /*
7531 * Don't handle the release after a long press, because it will
7532 * move the selection away from whatever the menu action was
7533 * trying to affect.
7534 */
Gilles Debunne2d373a12012-04-20 15:32:19 -07007535 if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
7536 mEditor.mDiscardNextActionUp = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007537 return superResult;
7538 }
7539
Gilles Debunne70a63122011-09-01 13:27:33 -07007540 final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) &&
Gilles Debunne2d373a12012-04-20 15:32:19 -07007541 (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
Gilles Debunnec3e85a72011-01-21 08:46:06 -08007542
Gilles Debunne70a63122011-09-01 13:27:33 -07007543 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
Janos Levai042856c2010-10-15 02:53:58 +03007544 && mText instanceof Spannable && mLayout != null) {
Gilles Debunnef788a9f2010-07-22 10:17:23 -07007545 boolean handled = false;
7546
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07007547 if (mMovement != null) {
7548 handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
7549 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007550
Gilles Debunne60e21862012-01-30 15:04:14 -08007551 final boolean textIsSelectable = isTextSelectable();
7552 if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
Gilles Debunnef3895ed2010-12-21 12:53:58 -08007553 // The LinkMovementMethod which should handle taps on links has not been installed
Gilles Debunne70a63122011-09-01 13:27:33 -07007554 // on non editable text that support text selection.
7555 // We reproduce its behavior here to open links for these.
Gilles Debunnef3895ed2010-12-21 12:53:58 -08007556 ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
7557 getSelectionEnd(), ClickableSpan.class);
7558
Gilles Debunne822b8f02012-01-17 18:02:15 -08007559 if (links.length > 0) {
Gilles Debunnef3895ed2010-12-21 12:53:58 -08007560 links[0].onClick(this);
7561 handled = true;
7562 }
7563 }
7564
Gilles Debunne60e21862012-01-30 15:04:14 -08007565 if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
Gilles Debunne180bb1b2011-03-10 11:14:00 -08007566 // Show the IME, except when selecting in read-only text.
satok863fcd62011-06-21 17:38:02 +09007567 final InputMethodManager imm = InputMethodManager.peekInstance();
satoka67a3cf2011-09-07 17:14:03 +09007568 viewClicked(imm);
Gilles Debunne3473b2b2012-04-20 16:21:10 -07007569 if (!textIsSelectable && mEditor.mShowSoftInputOnFocus) {
Gilles Debunne180bb1b2011-03-10 11:14:00 -08007570 handled |= imm != null && imm.showSoftInput(this, 0);
Adam Powell879fb6b2010-09-20 11:23:56 -07007571 }
Gilles Debunnebbb5d6e2010-06-21 16:21:51 -07007572
Gilles Debunned88876a2012-03-16 17:34:04 -07007573 // The above condition ensures that the mEditor is not null
Gilles Debunne2d373a12012-04-20 15:32:19 -07007574 mEditor.onTouchUpEvent(event);
Gilles Debunne6435a562011-08-04 21:22:30 -07007575
7576 handled = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007577 }
7578
The Android Open Source Project4df24232009-03-05 14:34:35 -08007579 if (handled) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007580 return true;
7581 }
7582 }
7583
7584 return superResult;
7585 }
7586
Jeff Brown8f345672011-02-26 13:29:53 -08007587 @Override
7588 public boolean onGenericMotionEvent(MotionEvent event) {
7589 if (mMovement != null && mText instanceof Spannable && mLayout != null) {
7590 try {
7591 if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) {
7592 return true;
7593 }
7594 } catch (AbstractMethodError ex) {
7595 // onGenericMotionEvent was added to the MovementMethod interface in API 12.
7596 // Ignore its absence in case third party applications implemented the
7597 // interface directly.
7598 }
7599 }
7600 return super.onGenericMotionEvent(event);
7601 }
7602
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007603 /**
Gilles Debunne86b9c782010-11-11 10:43:48 -08007604 * @return True iff this TextView contains a text that can be edited, or if this is
7605 * a selectable TextView.
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007606 */
Gilles Debunned88876a2012-03-16 17:34:04 -07007607 boolean isTextEditable() {
Gilles Debunnef076eeb2010-11-29 11:32:53 -08007608 return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007609 }
7610
The Android Open Source Project4df24232009-03-05 14:34:35 -08007611 /**
7612 * Returns true, only while processing a touch gesture, if the initial
7613 * 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 -07007614 * its selection changed. Only valid while processing the touch gesture
Gilles Debunne053c4392012-03-15 15:35:26 -07007615 * of interest, in an editable text view.
The Android Open Source Project4df24232009-03-05 14:34:35 -08007616 */
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07007617 public boolean didTouchFocusSelect() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007618 return mEditor != null && mEditor.mTouchFocusSelected;
The Android Open Source Project4df24232009-03-05 14:34:35 -08007619 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07007620
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007621 @Override
7622 public void cancelLongPress() {
7623 super.cancelLongPress();
Gilles Debunne2d373a12012-04-20 15:32:19 -07007624 if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007625 }
Gilles Debunne70a63122011-09-01 13:27:33 -07007626
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007627 @Override
7628 public boolean onTrackballEvent(MotionEvent event) {
Gilles Debunne60e21862012-01-30 15:04:14 -08007629 if (mMovement != null && mText instanceof Spannable && mLayout != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007630 if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
7631 return true;
7632 }
7633 }
7634
7635 return super.onTrackballEvent(event);
7636 }
7637
7638 public void setScroller(Scroller s) {
7639 mScroller = s;
7640 }
7641
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007642 @Override
7643 protected float getLeftFadingEdgeStrength() {
Adam Powell282e3772011-08-30 16:51:11 -07007644 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
7645 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007646 if (mMarquee != null && !mMarquee.isStopped()) {
7647 final Marquee marquee = mMarquee;
Romain Guyc2303192009-04-03 17:37:18 -07007648 if (marquee.shouldDrawLeftFade()) {
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07007649 final float scroll = marquee.getScroll();
7650 return scroll / getHorizontalFadingEdgeLength();
Romain Guyc2303192009-04-03 17:37:18 -07007651 } else {
7652 return 0.0f;
7653 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007654 } else if (getLineCount() == 1) {
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07007655 final int layoutDirection = getLayoutDirection();
Fabrice Di Meglioc0053222011-06-13 12:16:51 -07007656 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07007657 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007658 case Gravity.LEFT:
7659 return 0.0f;
7660 case Gravity.RIGHT:
7661 return (mLayout.getLineRight(0) - (mRight - mLeft) -
7662 getCompoundPaddingLeft() - getCompoundPaddingRight() -
7663 mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
7664 case Gravity.CENTER_HORIZONTAL:
7665 return 0.0f;
7666 }
7667 }
7668 }
7669 return super.getLeftFadingEdgeStrength();
7670 }
7671
7672 @Override
7673 protected float getRightFadingEdgeStrength() {
Adam Powell282e3772011-08-30 16:51:11 -07007674 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
7675 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007676 if (mMarquee != null && !mMarquee.isStopped()) {
7677 final Marquee marquee = mMarquee;
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07007678 final float maxFadeScroll = marquee.getMaxFadeScroll();
7679 final float scroll = marquee.getScroll();
7680 return (maxFadeScroll - scroll) / getHorizontalFadingEdgeLength();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007681 } else if (getLineCount() == 1) {
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07007682 final int layoutDirection = getLayoutDirection();
Fabrice Di Meglioc0053222011-06-13 12:16:51 -07007683 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07007684 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007685 case Gravity.LEFT:
Romain Guy076dc9f2009-06-24 17:17:51 -07007686 final int textWidth = (mRight - mLeft) - getCompoundPaddingLeft() -
7687 getCompoundPaddingRight();
7688 final float lineWidth = mLayout.getLineWidth(0);
7689 return (lineWidth - textWidth) / getHorizontalFadingEdgeLength();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007690 case Gravity.RIGHT:
7691 return 0.0f;
7692 case Gravity.CENTER_HORIZONTAL:
Gilles Debunne44c14732010-10-19 11:56:59 -07007693 case Gravity.FILL_HORIZONTAL:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007694 return (mLayout.getLineWidth(0) - ((mRight - mLeft) -
7695 getCompoundPaddingLeft() - getCompoundPaddingRight())) /
7696 getHorizontalFadingEdgeLength();
7697 }
7698 }
7699 }
7700 return super.getRightFadingEdgeStrength();
7701 }
7702
7703 @Override
7704 protected int computeHorizontalScrollRange() {
Romain Guydac5f9f2010-07-08 11:40:54 -07007705 if (mLayout != null) {
7706 return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT ?
7707 (int) mLayout.getLineWidth(0) : mLayout.getWidth();
7708 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007709
7710 return super.computeHorizontalScrollRange();
7711 }
7712
7713 @Override
7714 protected int computeVerticalScrollRange() {
7715 if (mLayout != null)
7716 return mLayout.getHeight();
7717
7718 return super.computeVerticalScrollRange();
7719 }
7720
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07007721 @Override
7722 protected int computeVerticalScrollExtent() {
7723 return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
7724 }
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07007725
7726 @Override
Svetoslav Ganovea515ae2011-09-14 18:15:32 -07007727 public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
7728 super.findViewsWithText(outViews, searched, flags);
7729 if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
7730 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
7731 String searchedLowerCase = searched.toString().toLowerCase();
7732 String textLowerCase = mText.toString().toLowerCase();
7733 if (textLowerCase.contains(searchedLowerCase)) {
7734 outViews.add(this);
7735 }
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07007736 }
7737 }
7738
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007739 public enum BufferType {
7740 NORMAL, SPANNABLE, EDITABLE,
7741 }
7742
7743 /**
7744 * Returns the TextView_textColor attribute from the
John Spurlock330dd532012-12-18 12:03:11 -05007745 * TypedArray, if set, or the TextAppearance_textColor
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007746 * from the TextView_textAppearance attribute, if TextView_textColor
7747 * was not set directly.
7748 */
7749 public static ColorStateList getTextColors(Context context, TypedArray attrs) {
7750 ColorStateList colors;
7751 colors = attrs.getColorStateList(com.android.internal.R.styleable.
7752 TextView_textColor);
7753
7754 if (colors == null) {
7755 int ap = attrs.getResourceId(com.android.internal.R.styleable.
7756 TextView_textAppearance, -1);
7757 if (ap != -1) {
7758 TypedArray appearance;
7759 appearance = context.obtainStyledAttributes(ap,
7760 com.android.internal.R.styleable.TextAppearance);
7761 colors = appearance.getColorStateList(com.android.internal.R.styleable.
7762 TextAppearance_textColor);
7763 appearance.recycle();
7764 }
7765 }
7766
7767 return colors;
7768 }
7769
7770 /**
7771 * Returns the default color from the TextView_textColor attribute
7772 * from the AttributeSet, if set, or the default color from the
7773 * TextAppearance_textColor from the TextView_textAppearance attribute,
7774 * if TextView_textColor was not set directly.
7775 */
7776 public static int getTextColor(Context context,
7777 TypedArray attrs,
7778 int def) {
7779 ColorStateList colors = getTextColors(context, attrs);
7780
7781 if (colors == null) {
7782 return def;
7783 } else {
7784 return colors.getDefaultColor();
7785 }
7786 }
7787
7788 @Override
7789 public boolean onKeyShortcut(int keyCode, KeyEvent event) {
Jeff Brownc1df9072010-12-21 16:38:50 -08007790 final int filteredMetaState = event.getMetaState() & ~KeyEvent.META_CTRL_MASK;
7791 if (KeyEvent.metaStateHasNoModifiers(filteredMetaState)) {
7792 switch (keyCode) {
7793 case KeyEvent.KEYCODE_A:
7794 if (canSelectText()) {
7795 return onTextContextMenuItem(ID_SELECT_ALL);
7796 }
7797 break;
7798 case KeyEvent.KEYCODE_X:
7799 if (canCut()) {
7800 return onTextContextMenuItem(ID_CUT);
7801 }
7802 break;
7803 case KeyEvent.KEYCODE_C:
7804 if (canCopy()) {
7805 return onTextContextMenuItem(ID_COPY);
7806 }
7807 break;
7808 case KeyEvent.KEYCODE_V:
7809 if (canPaste()) {
7810 return onTextContextMenuItem(ID_PASTE);
7811 }
7812 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007813 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007814 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007815 return super.onKeyShortcut(keyCode, event);
7816 }
7817
Gilles Debunnecbcb3452010-12-17 15:31:02 -08007818 /**
7819 * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
7820 * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
Gilles Debunne2d373a12012-04-20 15:32:19 -07007821 * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
7822 * sufficient.
Gilles Debunnecbcb3452010-12-17 15:31:02 -08007823 */
Gilles Debunnebaaace52010-10-01 15:47:13 -07007824 private boolean canSelectText() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007825 return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007826 }
7827
Gilles Debunnecbcb3452010-12-17 15:31:02 -08007828 /**
7829 * Test based on the <i>intrinsic</i> charateristics of the TextView.
7830 * The text must be spannable and the movement method must allow for arbitary selection.
Gilles Debunne2d373a12012-04-20 15:32:19 -07007831 *
Gilles Debunnecbcb3452010-12-17 15:31:02 -08007832 * See also {@link #canSelectText()}.
7833 */
Gilles Debunned88876a2012-03-16 17:34:04 -07007834 boolean textCanBeSelected() {
Gilles Debunne05336272010-07-09 20:13:45 -07007835 // prepareCursorController() relies on this method.
7836 // If you change this condition, make sure prepareCursorController is called anywhere
7837 // the value of this condition might be changed.
Gilles Debunnebb588da2011-07-11 18:26:19 -07007838 if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07007839 return isTextEditable() ||
7840 (isTextSelectable() && mText instanceof Spannable && isEnabled());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007841 }
7842
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09007843 private Locale getTextServicesLocale(boolean allowNullLocale) {
7844 // Start fetching the text services locale asynchronously.
7845 updateTextServicesLocaleAsync();
7846 // If !allowNullLocale and there is no cached text services locale, just return the default
7847 // locale.
7848 return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
7849 : mCurrentSpellCheckerLocaleCache;
7850 }
7851
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07007852 /**
7853 * This is a temporary method. Future versions may support multi-locale text.
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09007854 * Caveat: This method may not return the latest text services locale, but this should be
7855 * acceptable and it's more important to make this method asynchronous.
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07007856 *
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09007857 * @return The locale that should be used for a word iterator
satok05f24702011-11-02 19:29:35 +09007858 * in this TextView, based on the current spell checker settings,
7859 * the current IME's locale, or the system default locale.
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09007860 * Please note that a word iterator in this TextView is different from another word iterator
7861 * used by SpellChecker.java of TextView. This method should be used for the former.
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07007862 * @hide
7863 */
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09007864 // TODO: Support multi-locale
7865 // TODO: Update the text services locale immediately after the keyboard locale is switched
7866 // by catching intent of keyboard switch event
satok05f24702011-11-02 19:29:35 +09007867 public Locale getTextServicesLocale() {
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09007868 return getTextServicesLocale(false /* allowNullLocale */);
7869 }
7870
7871 /**
7872 * This is a temporary method. Future versions may support multi-locale text.
7873 * Caveat: This method may not return the latest spell checker locale, but this should be
7874 * acceptable and it's more important to make this method asynchronous.
7875 *
7876 * @return The locale that should be used for a spell checker in this TextView,
7877 * based on the current spell checker settings, the current IME's locale, or the system default
7878 * locale.
7879 * @hide
7880 */
7881 public Locale getSpellCheckerLocale() {
7882 return getTextServicesLocale(true /* allowNullLocale */);
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09007883 }
7884
7885 private void updateTextServicesLocaleAsync() {
7886 AsyncTask.execute(new Runnable() {
7887 @Override
7888 public void run() {
7889 if (mCurrentTextServicesLocaleLock.tryLock()) {
7890 try {
7891 updateTextServicesLocaleLocked();
7892 } finally {
7893 mCurrentTextServicesLocaleLock.unlock();
7894 }
7895 }
7896 }
7897 });
7898 }
7899
7900 private void updateTextServicesLocaleLocked() {
satok05f24702011-11-02 19:29:35 +09007901 final TextServicesManager textServicesManager = (TextServicesManager)
7902 mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
7903 final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09007904 final Locale locale;
satok05f24702011-11-02 19:29:35 +09007905 if (subtype != null) {
satokf927e172012-05-24 16:52:54 +09007906 locale = SpellCheckerSubtype.constructLocaleFromString(subtype.getLocale());
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09007907 } else {
7908 locale = null;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07007909 }
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09007910 mCurrentSpellCheckerLocaleCache = locale;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07007911 }
7912
7913 void onLocaleChanged() {
7914 // Will be re-created on demand in getWordIterator with the proper new locale
Gilles Debunne2d373a12012-04-20 15:32:19 -07007915 mEditor.mWordIterator = null;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07007916 }
7917
7918 /**
Gilles Debunned88876a2012-03-16 17:34:04 -07007919 * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
7920 * Made available to achieve a consistent behavior.
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07007921 * @hide
7922 */
7923 public WordIterator getWordIterator() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007924 if (mEditor != null) {
7925 return mEditor.getWordIterator();
Gilles Debunned88876a2012-03-16 17:34:04 -07007926 } else {
7927 return null;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07007928 }
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07007929 }
Gilles Debunnedf4ee432010-08-25 19:13:48 -07007930
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007931 @Override
Svetoslav Ganov736c2752011-04-22 18:30:36 -07007932 public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
Svetoslav Ganov887e1a12011-04-29 15:09:28 -07007933 super.onPopulateAccessibilityEvent(event);
7934
Svetoslav Ganov1d1e1102010-11-16 16:44:03 -08007935 final boolean isPassword = hasPasswordTransformationMethod();
alanv7d624192012-05-21 14:23:17 -07007936 if (!isPassword || shouldSpeakPasswordsForAccessibility()) {
7937 final CharSequence text = getTextForAccessibility();
Svetoslav Ganovd37848a2011-09-20 14:03:55 -07007938 if (!TextUtils.isEmpty(text)) {
svetoslavganov75986cf2009-05-14 22:28:01 -07007939 event.getText().add(text);
7940 }
svetoslavganov75986cf2009-05-14 22:28:01 -07007941 }
svetoslavganov75986cf2009-05-14 22:28:01 -07007942 }
7943
alanv7d624192012-05-21 14:23:17 -07007944 /**
7945 * @return true if the user has explicitly allowed accessibility services
7946 * to speak passwords.
7947 */
7948 private boolean shouldSpeakPasswordsForAccessibility() {
7949 return (Settings.Secure.getInt(mContext.getContentResolver(),
7950 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0) == 1);
7951 }
7952
Svetoslav Ganov30401322011-05-12 18:53:45 -07007953 @Override
7954 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
7955 super.onInitializeAccessibilityEvent(event);
7956
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08007957 event.setClassName(TextView.class.getName());
Svetoslav Ganov30401322011-05-12 18:53:45 -07007958 final boolean isPassword = hasPasswordTransformationMethod();
7959 event.setPassword(isPassword);
Svetoslav Ganova0156172011-06-26 17:55:44 -07007960
7961 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
7962 event.setFromIndex(Selection.getSelectionStart(mText));
7963 event.setToIndex(Selection.getSelectionEnd(mText));
7964 event.setItemCount(mText.length());
7965 }
Svetoslav Ganov30401322011-05-12 18:53:45 -07007966 }
7967
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07007968 @Override
7969 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
7970 super.onInitializeAccessibilityNodeInfo(info);
7971
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08007972 info.setClassName(TextView.class.getName());
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07007973 final boolean isPassword = hasPasswordTransformationMethod();
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08007974 info.setPassword(isPassword);
7975
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07007976 if (!isPassword) {
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07007977 info.setText(getTextForAccessibility());
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07007978 }
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07007979
Svetoslavbcc46a02013-02-06 11:56:00 -08007980 if (mBufferType == BufferType.EDITABLE) {
7981 info.setEditable(true);
7982 }
7983
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07007984 if (TextUtils.isEmpty(getContentDescription()) && !TextUtils.isEmpty(mText)) {
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07007985 info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
7986 info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
7987 info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
7988 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
7989 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
7990 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
7991 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
7992 }
Svetoslav7c512842013-01-30 23:02:08 -08007993 if (isFocused()) {
7994 if (canSelectText()) {
7995 info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
7996 }
7997 if (canCopy()) {
7998 info.addAction(AccessibilityNodeInfo.ACTION_COPY);
7999 }
8000 if (canPaste()) {
8001 info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
8002 }
8003 if (canCut()) {
8004 info.addAction(AccessibilityNodeInfo.ACTION_CUT);
8005 }
8006 }
8007 }
8008
8009 @Override
8010 public boolean performAccessibilityAction(int action, Bundle arguments) {
8011 switch (action) {
8012 case AccessibilityNodeInfo.ACTION_COPY: {
8013 if (isFocused() && canCopy()) {
8014 if (onTextContextMenuItem(ID_COPY)) {
8015 notifyAccessibilityStateChanged();
8016 return true;
8017 }
8018 }
8019 } return false;
8020 case AccessibilityNodeInfo.ACTION_PASTE: {
8021 if (isFocused() && canPaste()) {
8022 if (onTextContextMenuItem(ID_PASTE)) {
8023 notifyAccessibilityStateChanged();
8024 return true;
8025 }
8026 }
8027 } return false;
8028 case AccessibilityNodeInfo.ACTION_CUT: {
8029 if (isFocused() && canCut()) {
8030 if (onTextContextMenuItem(ID_CUT)) {
8031 notifyAccessibilityStateChanged();
8032 return true;
8033 }
8034 }
8035 } return false;
8036 case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
8037 if (isFocused() && canSelectText()) {
Svetoslav7c512842013-01-30 23:02:08 -08008038 CharSequence text = getIterableTextForAccessibility();
8039 if (text == null) {
8040 return false;
8041 }
Svetoslavd0c83cc2013-02-04 18:39:59 -08008042 final int start = (arguments != null) ? arguments.getInt(
8043 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
8044 final int end = (arguments != null) ? arguments.getInt(
8045 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
8046 if ((getSelectionStart() != start || getSelectionEnd() != end)) {
8047 // No arguments clears the selection.
8048 if (start == end && end == -1) {
8049 Selection.removeSelection((Spannable) text);
8050 notifyAccessibilityStateChanged();
8051 return true;
Svetoslav7c512842013-01-30 23:02:08 -08008052 }
Svetoslavd0c83cc2013-02-04 18:39:59 -08008053 if (start >= 0 && start <= end && end <= text.length()) {
8054 Selection.setSelection((Spannable) text, start, end);
8055 // Make sure selection mode is engaged.
8056 if (mEditor != null) {
8057 mEditor.startSelectionActionMode();
8058 }
8059 notifyAccessibilityStateChanged();
8060 return true;
8061 }
Svetoslav7c512842013-01-30 23:02:08 -08008062 }
8063 }
8064 } return false;
8065 default: {
8066 return super.performAccessibilityAction(action, arguments);
8067 }
8068 }
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07008069 }
8070
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07008071 @Override
8072 public void sendAccessibilityEvent(int eventType) {
8073 // Do not send scroll events since first they are not interesting for
8074 // accessibility and second such events a generated too frequently.
8075 // For details see the implementation of bringTextIntoView().
8076 if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
8077 return;
8078 }
8079 super.sendAccessibilityEvent(eventType);
8080 }
8081
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07008082 /**
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008083 * Gets the text reported for accessibility purposes.
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07008084 *
8085 * @return The accessibility text.
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008086 *
8087 * @hide
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07008088 */
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008089 public CharSequence getTextForAccessibility() {
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07008090 CharSequence text = getText();
8091 if (TextUtils.isEmpty(text)) {
8092 text = getHint();
8093 }
8094 return text;
8095 }
8096
svetoslavganov75986cf2009-05-14 22:28:01 -07008097 void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
8098 int fromIndex, int removedCount, int addedCount) {
8099 AccessibilityEvent event =
8100 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
8101 event.setFromIndex(fromIndex);
8102 event.setRemovedCount(removedCount);
8103 event.setAddedCount(addedCount);
8104 event.setBeforeText(beforeText);
8105 sendAccessibilityEventUnchecked(event);
8106 }
8107
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008108 /**
8109 * Returns whether this text view is a current input method target. The
8110 * default implementation just checks with {@link InputMethodManager}.
8111 */
8112 public boolean isInputMethodTarget() {
8113 InputMethodManager imm = InputMethodManager.peekInstance();
8114 return imm != null && imm.isActive(this);
8115 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07008116
Gilles Debunned88876a2012-03-16 17:34:04 -07008117 static final int ID_SELECT_ALL = android.R.id.selectAll;
8118 static final int ID_CUT = android.R.id.cut;
8119 static final int ID_COPY = android.R.id.copy;
8120 static final int ID_PASTE = android.R.id.paste;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008121
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008122 /**
8123 * Called when a context menu option for the text view is selected. Currently
Gilles Debunne07194e52011-11-02 14:18:44 -07008124 * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
8125 * {@link android.R.id#copy} or {@link android.R.id#paste}.
Gilles Debunnec59269f2011-04-22 11:46:09 -07008126 *
8127 * @return true if the context menu item action was performed.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008128 */
8129 public boolean onTextContextMenuItem(int id) {
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07008130 int min = 0;
8131 int max = mText.length();
Gilles Debunne64e54a62010-09-07 19:07:17 -07008132
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07008133 if (isFocused()) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07008134 final int selStart = getSelectionStart();
8135 final int selEnd = getSelectionEnd();
8136
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07008137 min = Math.max(0, Math.min(selStart, selEnd));
8138 max = Math.max(0, Math.max(selStart, selEnd));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008139 }
8140
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008141 switch (id) {
Jeff Brownc1df9072010-12-21 16:38:50 -08008142 case ID_SELECT_ALL:
Gilles Debunne299733e2011-02-07 17:11:41 -08008143 // This does not enter text selection mode. Text is highlighted, so that it can be
Gilles Debunnec59269f2011-04-22 11:46:09 -07008144 // bulk edited, like selectAllOnFocus does. Returns true even if text is empty.
Gilles Debunned88876a2012-03-16 17:34:04 -07008145 selectAllText();
Jeff Brownc1df9072010-12-21 16:38:50 -08008146 return true;
8147
8148 case ID_PASTE:
8149 paste(min, max);
8150 return true;
8151
8152 case ID_CUT:
Gilles Debunnecf68fee2011-09-29 10:55:36 -07008153 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
Gilles Debunne39ba6d92011-11-09 05:26:26 +01008154 deleteText_internal(min, max);
Jeff Brownc1df9072010-12-21 16:38:50 -08008155 stopSelectionActionMode();
8156 return true;
8157
8158 case ID_COPY:
Gilles Debunnecf68fee2011-09-29 10:55:36 -07008159 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
Jeff Brownc1df9072010-12-21 16:38:50 -08008160 stopSelectionActionMode();
8161 return true;
8162 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008163 return false;
8164 }
8165
Gilles Debunned88876a2012-03-16 17:34:04 -07008166 CharSequence getTransformedText(int start, int end) {
Gilles Debunnecf68fee2011-09-29 10:55:36 -07008167 return removeSuggestionSpans(mTransformed.subSequence(start, end));
8168 }
8169
Gilles Debunnee15b3582010-06-16 15:17:21 -07008170 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008171 public boolean performLongClick() {
Gilles Debunnee28454a2011-09-07 18:03:44 -07008172 boolean handled = false;
Gilles Debunnee28454a2011-09-07 18:03:44 -07008173
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008174 if (super.performLongClick()) {
Gilles Debunnee28454a2011-09-07 18:03:44 -07008175 handled = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008176 }
Gilles Debunnef170a342010-11-11 11:08:59 -08008177
Gilles Debunned88876a2012-03-16 17:34:04 -07008178 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008179 handled |= mEditor.performLongClick(handled);
Gilles Debunnee28454a2011-09-07 18:03:44 -07008180 }
8181
Gilles Debunne9f102ca2012-02-28 11:15:54 -08008182 if (handled) {
Gilles Debunnee28454a2011-09-07 18:03:44 -07008183 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
Gilles Debunne2d373a12012-04-20 15:32:19 -07008184 if (mEditor != null) mEditor.mDiscardNextActionUp = true;
Gilles Debunnef788a9f2010-07-22 10:17:23 -07008185 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008186
Gilles Debunne299733e2011-02-07 17:11:41 -08008187 return handled;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008188 }
8189
Gilles Debunne60e21862012-01-30 15:04:14 -08008190 @Override
8191 protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
8192 super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
Gilles Debunne6382ade2012-02-29 15:22:32 -08008193 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008194 mEditor.onScrollChanged();
Gilles Debunne60e21862012-01-30 15:04:14 -08008195 }
8196 }
8197
8198 /**
Gilles Debunne60e21862012-01-30 15:04:14 -08008199 * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
8200 * by the IME or by the spell checker as the user types. This is done by adding
8201 * {@link SuggestionSpan}s to the text.
8202 *
8203 * When suggestions are enabled (default), this list of suggestions will be displayed when the
8204 * user asks for them on these parts of the text. This value depends on the inputType of this
8205 * TextView.
8206 *
8207 * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
8208 *
8209 * In addition, the type variation must be one of
8210 * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
8211 * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
8212 * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
8213 * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
8214 * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
8215 *
8216 * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
8217 *
8218 * @return true if the suggestions popup window is enabled, based on the inputType.
8219 */
8220 public boolean isSuggestionsEnabled() {
8221 if (mEditor == null) return false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07008222 if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
8223 return false;
8224 }
8225 if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
Gilles Debunne60e21862012-01-30 15:04:14 -08008226
Gilles Debunne2d373a12012-04-20 15:32:19 -07008227 final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
Gilles Debunne60e21862012-01-30 15:04:14 -08008228 return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL ||
8229 variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT ||
8230 variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE ||
8231 variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE ||
8232 variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
8233 }
8234
8235 /**
8236 * If provided, this ActionMode.Callback will be used to create the ActionMode when text
8237 * selection is initiated in this View.
8238 *
8239 * The standard implementation populates the menu with a subset of Select All, Cut, Copy and
8240 * Paste actions, depending on what this View supports.
8241 *
8242 * A custom implementation can add new entries in the default menu in its
8243 * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, Menu)} method. The
8244 * default actions can also be removed from the menu using {@link Menu#removeItem(int)} and
8245 * passing {@link android.R.id#selectAll}, {@link android.R.id#cut}, {@link android.R.id#copy}
8246 * or {@link android.R.id#paste} ids as parameters.
8247 *
8248 * Returning false from
8249 * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, Menu)} will prevent
8250 * the action mode from being started.
8251 *
8252 * Action click events should be handled by the custom implementation of
8253 * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, MenuItem)}.
8254 *
8255 * Note that text selection mode is not started when a TextView receives focus and the
8256 * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
8257 * that case, to allow for quick replacement.
8258 */
8259 public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07008260 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07008261 mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
Gilles Debunne60e21862012-01-30 15:04:14 -08008262 }
8263
8264 /**
8265 * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
8266 *
8267 * @return The current custom selection callback.
8268 */
8269 public ActionMode.Callback getCustomSelectionActionModeCallback() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008270 return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
Gilles Debunne60e21862012-01-30 15:04:14 -08008271 }
8272
8273 /**
Gilles Debunne60e21862012-01-30 15:04:14 -08008274 * @hide
8275 */
8276 protected void stopSelectionActionMode() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008277 mEditor.stopSelectionActionMode();
Gilles Debunned88876a2012-03-16 17:34:04 -07008278 }
8279
8280 boolean canCut() {
8281 if (hasPasswordTransformationMethod()) {
8282 return false;
Gilles Debunne60e21862012-01-30 15:04:14 -08008283 }
Gilles Debunned88876a2012-03-16 17:34:04 -07008284
Gilles Debunne2d373a12012-04-20 15:32:19 -07008285 if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null &&
8286 mEditor.mKeyListener != null) {
Gilles Debunned88876a2012-03-16 17:34:04 -07008287 return true;
8288 }
8289
8290 return false;
8291 }
8292
8293 boolean canCopy() {
8294 if (hasPasswordTransformationMethod()) {
8295 return false;
8296 }
8297
8298 if (mText.length() > 0 && hasSelection()) {
8299 return true;
8300 }
8301
8302 return false;
8303 }
8304
8305 boolean canPaste() {
8306 return (mText instanceof Editable &&
Gilles Debunne2d373a12012-04-20 15:32:19 -07008307 mEditor != null && mEditor.mKeyListener != null &&
Gilles Debunned88876a2012-03-16 17:34:04 -07008308 getSelectionStart() >= 0 &&
8309 getSelectionEnd() >= 0 &&
8310 ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
8311 hasPrimaryClip());
8312 }
8313
8314 boolean selectAllText() {
8315 final int length = mText.length();
8316 Selection.setSelection((Spannable) mText, 0, length);
8317 return length > 0;
8318 }
8319
8320 /**
8321 * Prepare text so that there are not zero or two spaces at beginning and end of region defined
8322 * by [min, max] when replacing this region by paste.
8323 * Note that if there were two spaces (or more) at that position before, they are kept. We just
8324 * make sure we do not add an extra one from the paste content.
8325 */
8326 long prepareSpacesAroundPaste(int min, int max, CharSequence paste) {
8327 if (paste.length() > 0) {
8328 if (min > 0) {
8329 final char charBefore = mTransformed.charAt(min - 1);
8330 final char charAfter = paste.charAt(0);
8331
8332 if (Character.isSpaceChar(charBefore) && Character.isSpaceChar(charAfter)) {
8333 // Two spaces at beginning of paste: remove one
8334 final int originalLength = mText.length();
8335 deleteText_internal(min - 1, min);
8336 // Due to filters, there is no guarantee that exactly one character was
8337 // removed: count instead.
8338 final int delta = mText.length() - originalLength;
8339 min += delta;
8340 max += delta;
8341 } else if (!Character.isSpaceChar(charBefore) && charBefore != '\n' &&
8342 !Character.isSpaceChar(charAfter) && charAfter != '\n') {
8343 // No space at beginning of paste: add one
8344 final int originalLength = mText.length();
8345 replaceText_internal(min, min, " ");
8346 // Taking possible filters into account as above.
8347 final int delta = mText.length() - originalLength;
8348 min += delta;
8349 max += delta;
8350 }
8351 }
8352
8353 if (max < mText.length()) {
8354 final char charBefore = paste.charAt(paste.length() - 1);
8355 final char charAfter = mTransformed.charAt(max);
8356
8357 if (Character.isSpaceChar(charBefore) && Character.isSpaceChar(charAfter)) {
8358 // Two spaces at end of paste: remove one
8359 deleteText_internal(max, max + 1);
8360 } else if (!Character.isSpaceChar(charBefore) && charBefore != '\n' &&
8361 !Character.isSpaceChar(charAfter) && charAfter != '\n') {
8362 // No space at end of paste: add one
8363 replaceText_internal(max, max, " ");
8364 }
8365 }
8366 }
8367
8368 return TextUtils.packRangeInLong(min, max);
Gilles Debunne60e21862012-01-30 15:04:14 -08008369 }
8370
8371 /**
8372 * Paste clipboard content between min and max positions.
8373 */
8374 private void paste(int min, int max) {
8375 ClipboardManager clipboard =
8376 (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
8377 ClipData clip = clipboard.getPrimaryClip();
8378 if (clip != null) {
8379 boolean didFirst = false;
8380 for (int i=0; i<clip.getItemCount(); i++) {
Dianne Hackbornacb69bb2012-04-13 15:36:06 -07008381 CharSequence paste = clip.getItemAt(i).coerceToStyledText(getContext());
Gilles Debunne60e21862012-01-30 15:04:14 -08008382 if (paste != null) {
8383 if (!didFirst) {
8384 long minMax = prepareSpacesAroundPaste(min, max, paste);
Gilles Debunne6c488de2012-03-01 16:20:35 -08008385 min = TextUtils.unpackRangeStartFromLong(minMax);
8386 max = TextUtils.unpackRangeEndFromLong(minMax);
Gilles Debunne60e21862012-01-30 15:04:14 -08008387 Selection.setSelection((Spannable) mText, max);
8388 ((Editable) mText).replace(min, max, paste);
8389 didFirst = true;
8390 } else {
8391 ((Editable) mText).insert(getSelectionEnd(), "\n");
8392 ((Editable) mText).insert(getSelectionEnd(), paste);
8393 }
8394 }
8395 }
8396 stopSelectionActionMode();
8397 LAST_CUT_OR_COPY_TIME = 0;
8398 }
8399 }
8400
8401 private void setPrimaryClip(ClipData clip) {
8402 ClipboardManager clipboard = (ClipboardManager) getContext().
8403 getSystemService(Context.CLIPBOARD_SERVICE);
8404 clipboard.setPrimaryClip(clip);
8405 LAST_CUT_OR_COPY_TIME = SystemClock.uptimeMillis();
8406 }
8407
Gilles Debunne60e21862012-01-30 15:04:14 -08008408 /**
8409 * Get the character offset closest to the specified absolute position. A typical use case is to
8410 * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
8411 *
8412 * @param x The horizontal absolute position of a point on screen
8413 * @param y The vertical absolute position of a point on screen
8414 * @return the character offset for the character whose position is closest to the specified
8415 * position. Returns -1 if there is no layout.
8416 */
8417 public int getOffsetForPosition(float x, float y) {
8418 if (getLayout() == null) return -1;
8419 final int line = getLineAtCoordinate(y);
8420 final int offset = getOffsetAtCoordinate(line, x);
8421 return offset;
8422 }
8423
Gilles Debunned88876a2012-03-16 17:34:04 -07008424 float convertToLocalHorizontalCoordinate(float x) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008425 x -= getTotalPaddingLeft();
8426 // Clamp the position to inside of the view.
8427 x = Math.max(0.0f, x);
8428 x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
8429 x += getScrollX();
8430 return x;
8431 }
8432
Gilles Debunned88876a2012-03-16 17:34:04 -07008433 int getLineAtCoordinate(float y) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008434 y -= getTotalPaddingTop();
8435 // Clamp the position to inside of the view.
8436 y = Math.max(0.0f, y);
8437 y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
8438 y += getScrollY();
8439 return getLayout().getLineForVertical((int) y);
8440 }
8441
8442 private int getOffsetAtCoordinate(int line, float x) {
8443 x = convertToLocalHorizontalCoordinate(x);
8444 return getLayout().getOffsetForHorizontal(line, x);
8445 }
8446
Gilles Debunne60e21862012-01-30 15:04:14 -08008447 @Override
8448 public boolean onDragEvent(DragEvent event) {
8449 switch (event.getAction()) {
8450 case DragEvent.ACTION_DRAG_STARTED:
Gilles Debunne2d373a12012-04-20 15:32:19 -07008451 return mEditor != null && mEditor.hasInsertionController();
Gilles Debunne60e21862012-01-30 15:04:14 -08008452
8453 case DragEvent.ACTION_DRAG_ENTERED:
8454 TextView.this.requestFocus();
8455 return true;
8456
8457 case DragEvent.ACTION_DRAG_LOCATION:
8458 final int offset = getOffsetForPosition(event.getX(), event.getY());
8459 Selection.setSelection((Spannable)mText, offset);
8460 return true;
8461
8462 case DragEvent.ACTION_DROP:
Gilles Debunne2d373a12012-04-20 15:32:19 -07008463 if (mEditor != null) mEditor.onDrop(event);
Gilles Debunne60e21862012-01-30 15:04:14 -08008464 return true;
8465
8466 case DragEvent.ACTION_DRAG_ENDED:
8467 case DragEvent.ACTION_DRAG_EXITED:
8468 default:
8469 return true;
8470 }
8471 }
8472
Gilles Debunne60e21862012-01-30 15:04:14 -08008473 boolean isInBatchEditMode() {
8474 if (mEditor == null) return false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07008475 final Editor.InputMethodState ims = mEditor.mInputMethodState;
Gilles Debunne60e21862012-01-30 15:04:14 -08008476 if (ims != null) {
8477 return ims.mBatchEditNesting > 0;
8478 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07008479 return mEditor.mInBatchEditControllers;
Gilles Debunne60e21862012-01-30 15:04:14 -08008480 }
8481
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008482 TextDirectionHeuristic getTextDirectionHeuristic() {
Gilles Debunne60e21862012-01-30 15:04:14 -08008483 if (hasPasswordTransformationMethod()) {
Fabrice Di Meglio8701bb92012-11-14 19:57:11 -08008484 // passwords fields should be LTR
8485 return TextDirectionHeuristics.LTR;
Gilles Debunne60e21862012-01-30 15:04:14 -08008486 }
8487
8488 // Always need to resolve layout direction first
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07008489 final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
Gilles Debunne60e21862012-01-30 15:04:14 -08008490
8491 // Now, we can select the heuristic
Fabrice Di Meglio97e146c2012-09-23 15:45:16 -07008492 switch (getTextDirection()) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008493 default:
8494 case TEXT_DIRECTION_FIRST_STRONG:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008495 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
Gilles Debunne60e21862012-01-30 15:04:14 -08008496 TextDirectionHeuristics.FIRSTSTRONG_LTR);
Gilles Debunne60e21862012-01-30 15:04:14 -08008497 case TEXT_DIRECTION_ANY_RTL:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008498 return TextDirectionHeuristics.ANYRTL_LTR;
Gilles Debunne60e21862012-01-30 15:04:14 -08008499 case TEXT_DIRECTION_LTR:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008500 return TextDirectionHeuristics.LTR;
Gilles Debunne60e21862012-01-30 15:04:14 -08008501 case TEXT_DIRECTION_RTL:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008502 return TextDirectionHeuristics.RTL;
Gilles Debunne60e21862012-01-30 15:04:14 -08008503 case TEXT_DIRECTION_LOCALE:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008504 return TextDirectionHeuristics.LOCALE;
Gilles Debunne60e21862012-01-30 15:04:14 -08008505 }
8506 }
8507
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008508 /**
8509 * @hide
8510 */
Fabrice Di Megliob03b4342012-06-04 12:55:30 -07008511 @Override
8512 public void onResolveDrawables(int layoutDirection) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008513 // No need to resolve twice
Fabrice Di Meglio1957d282012-10-25 17:42:39 -07008514 if (mLastLayoutDirection == layoutDirection) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008515 return;
8516 }
Fabrice Di Meglio1957d282012-10-25 17:42:39 -07008517 mLastLayoutDirection = layoutDirection;
Gilles Debunne60e21862012-01-30 15:04:14 -08008518
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -08008519 // Resolve drawables
8520 if (mDrawables != null) {
8521 mDrawables.resolveWithLayoutDirection(layoutDirection);
Fabrice Di Megliob03b4342012-06-04 12:55:30 -07008522 }
8523 }
8524
Fabrice Di Meglio84ebb352012-10-11 16:27:37 -07008525 /**
8526 * @hide
8527 */
Gilles Debunne60e21862012-01-30 15:04:14 -08008528 protected void resetResolvedDrawables() {
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -08008529 super.resetResolvedDrawables();
Fabrice Di Meglio1957d282012-10-25 17:42:39 -07008530 mLastLayoutDirection = -1;
Gilles Debunne60e21862012-01-30 15:04:14 -08008531 }
8532
8533 /**
8534 * @hide
8535 */
8536 protected void viewClicked(InputMethodManager imm) {
8537 if (imm != null) {
8538 imm.viewClicked(this);
8539 }
8540 }
8541
8542 /**
8543 * Deletes the range of text [start, end[.
8544 * @hide
8545 */
8546 protected void deleteText_internal(int start, int end) {
8547 ((Editable) mText).delete(start, end);
8548 }
8549
8550 /**
8551 * Replaces the range of text [start, end[ by replacement text
8552 * @hide
8553 */
8554 protected void replaceText_internal(int start, int end, CharSequence text) {
8555 ((Editable) mText).replace(start, end, text);
8556 }
8557
8558 /**
8559 * Sets a span on the specified range of text
8560 * @hide
8561 */
8562 protected void setSpan_internal(Object span, int start, int end, int flags) {
8563 ((Editable) mText).setSpan(span, start, end, flags);
8564 }
8565
8566 /**
8567 * Moves the cursor to the specified offset position in text
8568 * @hide
8569 */
8570 protected void setCursorPosition_internal(int start, int end) {
8571 Selection.setSelection(((Editable) mText), start, end);
8572 }
8573
8574 /**
8575 * An Editor should be created as soon as any of the editable-specific fields (grouped
8576 * inside the Editor object) is assigned to a non-default value.
8577 * This method will create the Editor if needed.
8578 *
8579 * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
8580 * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
8581 * Editor for backward compatibility, as soon as one of these fields is assigned.
8582 *
8583 * Also note that for performance reasons, the mEditor is created when needed, but not
8584 * reset when no more edit-specific fields are needed.
8585 */
Gilles Debunne5fae9962012-05-08 14:53:20 -07008586 private void createEditorIfNeeded() {
Gilles Debunne60e21862012-01-30 15:04:14 -08008587 if (mEditor == null) {
Gilles Debunned88876a2012-03-16 17:34:04 -07008588 mEditor = new Editor(this);
Gilles Debunne60e21862012-01-30 15:04:14 -08008589 }
8590 }
8591
Gilles Debunne60e21862012-01-30 15:04:14 -08008592 /**
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008593 * @hide
8594 */
8595 @Override
8596 public CharSequence getIterableTextForAccessibility() {
Svetoslav Ganov2f4bf522012-09-06 19:07:44 -07008597 if (!TextUtils.isEmpty(mText)) {
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008598 if (!(mText instanceof Spannable)) {
8599 setText(mText, BufferType.SPANNABLE);
8600 }
8601 return mText;
8602 }
8603 return super.getIterableTextForAccessibility();
8604 }
8605
8606 /**
8607 * @hide
8608 */
8609 @Override
8610 public TextSegmentIterator getIteratorForGranularity(int granularity) {
8611 switch (granularity) {
8612 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
8613 Spannable text = (Spannable) getIterableTextForAccessibility();
8614 if (!TextUtils.isEmpty(text) && getLayout() != null) {
8615 AccessibilityIterators.LineTextSegmentIterator iterator =
8616 AccessibilityIterators.LineTextSegmentIterator.getInstance();
8617 iterator.initialize(text, getLayout());
8618 return iterator;
8619 }
8620 } break;
8621 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
8622 Spannable text = (Spannable) getIterableTextForAccessibility();
8623 if (!TextUtils.isEmpty(text) && getLayout() != null) {
8624 AccessibilityIterators.PageTextSegmentIterator iterator =
8625 AccessibilityIterators.PageTextSegmentIterator.getInstance();
8626 iterator.initialize(this);
8627 return iterator;
8628 }
8629 } break;
8630 }
8631 return super.getIteratorForGranularity(granularity);
8632 }
8633
8634 /**
8635 * @hide
8636 */
8637 @Override
Svetoslav7c512842013-01-30 23:02:08 -08008638 public int getAccessibilitySelectionStart() {
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008639 if (TextUtils.isEmpty(getContentDescription())) {
Svetoslav7c512842013-01-30 23:02:08 -08008640 final int selectionStart = getSelectionStart();
8641 if (selectionStart >= 0) {
8642 return selectionStart;
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -07008643 }
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008644 }
Svetoslav7c512842013-01-30 23:02:08 -08008645 return ACCESSIBILITY_CURSOR_POSITION_UNDEFINED;
8646 }
8647
8648 /**
8649 * @hide
8650 */
8651 public boolean isAccessibilitySelectionExtendable() {
8652 return true;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008653 }
8654
8655 /**
8656 * @hide
8657 */
8658 @Override
Svetoslav7c512842013-01-30 23:02:08 -08008659 public int getAccessibilitySelectionEnd() {
8660 if (TextUtils.isEmpty(getContentDescription())) {
8661 final int selectionEnd = getSelectionEnd();
8662 if (selectionEnd >= 0) {
8663 return selectionEnd;
8664 }
8665 }
8666 return ACCESSIBILITY_CURSOR_POSITION_UNDEFINED;
8667 }
8668
8669 /**
8670 * @hide
8671 */
8672 @Override
8673 public void setAccessibilitySelection(int start, int end) {
8674 if (getAccessibilitySelectionStart() == start
8675 && getAccessibilitySelectionEnd() == end) {
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008676 return;
8677 }
Svetoslav7c512842013-01-30 23:02:08 -08008678 CharSequence text = getIterableTextForAccessibility();
8679 if (start >= 0 && start <= end && end <= text.length()) {
8680 Selection.setSelection((Spannable) text, start, end);
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008681 } else {
Svetoslav7c512842013-01-30 23:02:08 -08008682 Selection.removeSelection((Spannable) text);
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008683 }
8684 }
8685
8686 /**
Gilles Debunne60e21862012-01-30 15:04:14 -08008687 * User interface state that is stored by TextView for implementing
8688 * {@link View#onSaveInstanceState}.
8689 */
8690 public static class SavedState extends BaseSavedState {
8691 int selStart;
8692 int selEnd;
8693 CharSequence text;
8694 boolean frozenWithFocus;
8695 CharSequence error;
8696
8697 SavedState(Parcelable superState) {
8698 super(superState);
8699 }
8700
8701 @Override
8702 public void writeToParcel(Parcel out, int flags) {
8703 super.writeToParcel(out, flags);
8704 out.writeInt(selStart);
8705 out.writeInt(selEnd);
8706 out.writeInt(frozenWithFocus ? 1 : 0);
8707 TextUtils.writeToParcel(text, out, flags);
8708
8709 if (error == null) {
8710 out.writeInt(0);
8711 } else {
8712 out.writeInt(1);
8713 TextUtils.writeToParcel(error, out, flags);
8714 }
8715 }
8716
8717 @Override
8718 public String toString() {
8719 String str = "TextView.SavedState{"
8720 + Integer.toHexString(System.identityHashCode(this))
8721 + " start=" + selStart + " end=" + selEnd;
8722 if (text != null) {
8723 str += " text=" + text;
8724 }
8725 return str + "}";
8726 }
8727
8728 @SuppressWarnings("hiding")
8729 public static final Parcelable.Creator<SavedState> CREATOR
8730 = new Parcelable.Creator<SavedState>() {
8731 public SavedState createFromParcel(Parcel in) {
8732 return new SavedState(in);
8733 }
8734
8735 public SavedState[] newArray(int size) {
8736 return new SavedState[size];
8737 }
8738 };
8739
8740 private SavedState(Parcel in) {
8741 super(in);
8742 selStart = in.readInt();
8743 selEnd = in.readInt();
8744 frozenWithFocus = (in.readInt() != 0);
8745 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
8746
8747 if (in.readInt() != 0) {
8748 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
8749 }
8750 }
8751 }
8752
8753 private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
8754 private char[] mChars;
8755 private int mStart, mLength;
8756
8757 public CharWrapper(char[] chars, int start, int len) {
8758 mChars = chars;
8759 mStart = start;
8760 mLength = len;
8761 }
8762
8763 /* package */ void set(char[] chars, int start, int len) {
8764 mChars = chars;
8765 mStart = start;
8766 mLength = len;
8767 }
8768
8769 public int length() {
8770 return mLength;
8771 }
8772
8773 public char charAt(int off) {
8774 return mChars[off + mStart];
8775 }
8776
8777 @Override
8778 public String toString() {
8779 return new String(mChars, mStart, mLength);
8780 }
8781
8782 public CharSequence subSequence(int start, int end) {
8783 if (start < 0 || end < 0 || start > mLength || end > mLength) {
8784 throw new IndexOutOfBoundsException(start + ", " + end);
8785 }
8786
8787 return new String(mChars, start + mStart, end - start);
8788 }
8789
8790 public void getChars(int start, int end, char[] buf, int off) {
8791 if (start < 0 || end < 0 || start > mLength || end > mLength) {
8792 throw new IndexOutOfBoundsException(start + ", " + end);
8793 }
8794
8795 System.arraycopy(mChars, start + mStart, buf, off, end - start);
8796 }
8797
8798 public void drawText(Canvas c, int start, int end,
8799 float x, float y, Paint p) {
8800 c.drawText(mChars, start + mStart, end - start, x, y, p);
8801 }
8802
8803 public void drawTextRun(Canvas c, int start, int end,
Fabrice Di Meglio6d9fe5b2013-02-11 18:27:34 -08008804 int contextStart, int contextEnd, float x, float y, Paint p) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008805 int count = end - start;
8806 int contextCount = contextEnd - contextStart;
8807 c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
Fabrice Di Meglio6d9fe5b2013-02-11 18:27:34 -08008808 contextCount, x, y, p);
Gilles Debunne60e21862012-01-30 15:04:14 -08008809 }
8810
8811 public float measureText(int start, int end, Paint p) {
8812 return p.measureText(mChars, start + mStart, end - start);
8813 }
8814
8815 public int getTextWidths(int start, int end, float[] widths, Paint p) {
8816 return p.getTextWidths(mChars, start + mStart, end - start, widths);
8817 }
8818
8819 public float getTextRunAdvances(int start, int end, int contextStart,
Fabrice Di Meglio6d9fe5b2013-02-11 18:27:34 -08008820 int contextEnd, float[] advances, int advancesIndex,
Gilles Debunne60e21862012-01-30 15:04:14 -08008821 Paint p) {
8822 int count = end - start;
8823 int contextCount = contextEnd - contextStart;
8824 return p.getTextRunAdvances(mChars, start + mStart, count,
Fabrice Di Meglio6d9fe5b2013-02-11 18:27:34 -08008825 contextStart + mStart, contextCount, advances,
Gilles Debunne60e21862012-01-30 15:04:14 -08008826 advancesIndex);
8827 }
8828
Fabrice Di Meglio6d9fe5b2013-02-11 18:27:34 -08008829 public int getTextRunCursor(int contextStart, int contextEnd,
Gilles Debunne60e21862012-01-30 15:04:14 -08008830 int offset, int cursorOpt, Paint p) {
8831 int contextCount = contextEnd - contextStart;
8832 return p.getTextRunCursor(mChars, contextStart + mStart,
Fabrice Di Meglio6d9fe5b2013-02-11 18:27:34 -08008833 contextCount, offset + mStart, cursorOpt);
Gilles Debunne60e21862012-01-30 15:04:14 -08008834 }
8835 }
8836
Gilles Debunne60e21862012-01-30 15:04:14 -08008837 private static final class Marquee extends Handler {
8838 // TODO: Add an option to configure this
8839 private static final float MARQUEE_DELTA_MAX = 0.07f;
8840 private static final int MARQUEE_DELAY = 1200;
8841 private static final int MARQUEE_RESTART_DELAY = 1200;
8842 private static final int MARQUEE_RESOLUTION = 1000 / 30;
8843 private static final int MARQUEE_PIXELS_PER_SECOND = 30;
8844
8845 private static final byte MARQUEE_STOPPED = 0x0;
8846 private static final byte MARQUEE_STARTING = 0x1;
8847 private static final byte MARQUEE_RUNNING = 0x2;
8848
8849 private static final int MESSAGE_START = 0x1;
8850 private static final int MESSAGE_TICK = 0x2;
8851 private static final int MESSAGE_RESTART = 0x3;
8852
8853 private final WeakReference<TextView> mView;
8854
8855 private byte mStatus = MARQUEE_STOPPED;
8856 private final float mScrollUnit;
8857 private float mMaxScroll;
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07008858 private float mMaxFadeScroll;
Gilles Debunne60e21862012-01-30 15:04:14 -08008859 private float mGhostStart;
8860 private float mGhostOffset;
8861 private float mFadeStop;
8862 private int mRepeatLimit;
8863
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07008864 private float mScroll;
Gilles Debunne60e21862012-01-30 15:04:14 -08008865
8866 Marquee(TextView v) {
8867 final float density = v.getContext().getResources().getDisplayMetrics().density;
8868 mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / MARQUEE_RESOLUTION;
8869 mView = new WeakReference<TextView>(v);
8870 }
8871
8872 @Override
8873 public void handleMessage(Message msg) {
8874 switch (msg.what) {
8875 case MESSAGE_START:
8876 mStatus = MARQUEE_RUNNING;
8877 tick();
8878 break;
8879 case MESSAGE_TICK:
8880 tick();
8881 break;
8882 case MESSAGE_RESTART:
8883 if (mStatus == MARQUEE_RUNNING) {
8884 if (mRepeatLimit >= 0) {
8885 mRepeatLimit--;
8886 }
8887 start(mRepeatLimit);
8888 }
8889 break;
8890 }
8891 }
8892
8893 void tick() {
8894 if (mStatus != MARQUEE_RUNNING) {
8895 return;
8896 }
8897
8898 removeMessages(MESSAGE_TICK);
8899
8900 final TextView textView = mView.get();
8901 if (textView != null && (textView.isFocused() || textView.isSelected())) {
8902 mScroll += mScrollUnit;
8903 if (mScroll > mMaxScroll) {
8904 mScroll = mMaxScroll;
8905 sendEmptyMessageDelayed(MESSAGE_RESTART, MARQUEE_RESTART_DELAY);
8906 } else {
8907 sendEmptyMessageDelayed(MESSAGE_TICK, MARQUEE_RESOLUTION);
8908 }
8909 textView.invalidate();
8910 }
8911 }
8912
8913 void stop() {
8914 mStatus = MARQUEE_STOPPED;
8915 removeMessages(MESSAGE_START);
8916 removeMessages(MESSAGE_RESTART);
8917 removeMessages(MESSAGE_TICK);
8918 resetScroll();
8919 }
8920
8921 private void resetScroll() {
8922 mScroll = 0.0f;
8923 final TextView textView = mView.get();
8924 if (textView != null) textView.invalidate();
8925 }
8926
8927 void start(int repeatLimit) {
8928 if (repeatLimit == 0) {
8929 stop();
8930 return;
8931 }
8932 mRepeatLimit = repeatLimit;
8933 final TextView textView = mView.get();
8934 if (textView != null && textView.mLayout != null) {
8935 mStatus = MARQUEE_STARTING;
8936 mScroll = 0.0f;
8937 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
8938 textView.getCompoundPaddingRight();
8939 final float lineWidth = textView.mLayout.getLineWidth(0);
8940 final float gap = textWidth / 3.0f;
8941 mGhostStart = lineWidth - textWidth + gap;
8942 mMaxScroll = mGhostStart + textWidth;
8943 mGhostOffset = lineWidth + gap;
8944 mFadeStop = lineWidth + textWidth / 6.0f;
8945 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
8946
8947 textView.invalidate();
8948 sendEmptyMessageDelayed(MESSAGE_START, MARQUEE_DELAY);
8949 }
8950 }
8951
8952 float getGhostOffset() {
8953 return mGhostOffset;
8954 }
8955
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07008956 float getScroll() {
8957 return mScroll;
8958 }
8959
8960 float getMaxFadeScroll() {
8961 return mMaxFadeScroll;
8962 }
8963
Gilles Debunne60e21862012-01-30 15:04:14 -08008964 boolean shouldDrawLeftFade() {
8965 return mScroll <= mFadeStop;
8966 }
8967
8968 boolean shouldDrawGhost() {
8969 return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
8970 }
8971
8972 boolean isRunning() {
8973 return mStatus == MARQUEE_RUNNING;
8974 }
8975
8976 boolean isStopped() {
8977 return mStatus == MARQUEE_STOPPED;
8978 }
8979 }
8980
Gilles Debunne60e21862012-01-30 15:04:14 -08008981 private class ChangeWatcher implements TextWatcher, SpanWatcher {
8982
8983 private CharSequence mBeforeText;
8984
Gilles Debunne60e21862012-01-30 15:04:14 -08008985 public void beforeTextChanged(CharSequence buffer, int start,
8986 int before, int after) {
8987 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start
8988 + " before=" + before + " after=" + after + ": " + buffer);
8989
8990 if (AccessibilityManager.getInstance(mContext).isEnabled()
Svetoslav Ganov72bba582012-11-05 13:53:43 -08008991 && ((!isPasswordInputType(getInputType()) && !hasPasswordTransformationMethod())
8992 || shouldSpeakPasswordsForAccessibility())) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008993 mBeforeText = buffer.toString();
8994 }
8995
8996 TextView.this.sendBeforeTextChanged(buffer, start, before, after);
8997 }
8998
Gilles Debunned88876a2012-03-16 17:34:04 -07008999 public void onTextChanged(CharSequence buffer, int start, int before, int after) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009000 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start
9001 + " before=" + before + " after=" + after + ": " + buffer);
9002 TextView.this.handleTextChanged(buffer, start, before, after);
9003
Gilles Debunne60e21862012-01-30 15:04:14 -08009004 if (AccessibilityManager.getInstance(mContext).isEnabled() &&
9005 (isFocused() || isSelected() && isShown())) {
9006 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
9007 mBeforeText = null;
9008 }
9009 }
9010
9011 public void afterTextChanged(Editable buffer) {
9012 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer);
9013 TextView.this.sendAfterTextChanged(buffer);
9014
9015 if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
9016 MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
9017 }
9018 }
9019
Gilles Debunned88876a2012-03-16 17:34:04 -07009020 public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009021 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
9022 + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
9023 TextView.this.spanChange(buf, what, s, st, e, en);
9024 }
9025
9026 public void onSpanAdded(Spannable buf, Object what, int s, int e) {
9027 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e
9028 + " what=" + what + ": " + buf);
9029 TextView.this.spanChange(buf, what, -1, s, -1, e);
9030 }
9031
9032 public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
9033 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e
9034 + " what=" + what + ": " + buf);
9035 TextView.this.spanChange(buf, what, s, -1, e, -1);
9036 }
satoka67a3cf2011-09-07 17:14:03 +09009037 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009038}