blob: 04c407032d6321a62d96151a7e6ae136eb71bed8 [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
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700139import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
140
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800141/**
142 * Displays text to the user and optionally allows them to edit it. A TextView
143 * is a complete text editor, however the basic class is configured to not
144 * allow editing; see {@link EditText} for a subclass that configures the text
145 * view for editing.
146 *
147 * <p>
Joe Malin10d96952013-05-29 17:49:09 -0700148 * To allow users to copy some or all of the TextView's value and paste it somewhere else, set the
149 * XML attribute {@link android.R.styleable#TextView_textIsSelectable
150 * android:textIsSelectable} to "true" or call
151 * {@link #setTextIsSelectable setTextIsSelectable(true)}. The {@code textIsSelectable} flag
152 * allows users to make selection gestures in the TextView, which in turn triggers the system's
153 * built-in copy/paste controls.
154 * <p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800155 * <b>XML attributes</b>
156 * <p>
157 * See {@link android.R.styleable#TextView TextView Attributes},
158 * {@link android.R.styleable#View View Attributes}
159 *
160 * @attr ref android.R.styleable#TextView_text
161 * @attr ref android.R.styleable#TextView_bufferType
162 * @attr ref android.R.styleable#TextView_hint
163 * @attr ref android.R.styleable#TextView_textColor
164 * @attr ref android.R.styleable#TextView_textColorHighlight
165 * @attr ref android.R.styleable#TextView_textColorHint
Romain Guyd6a463a2009-05-21 23:10:10 -0700166 * @attr ref android.R.styleable#TextView_textAppearance
167 * @attr ref android.R.styleable#TextView_textColorLink
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800168 * @attr ref android.R.styleable#TextView_textSize
169 * @attr ref android.R.styleable#TextView_textScaleX
Raph Leviend570e892012-05-09 11:45:34 -0700170 * @attr ref android.R.styleable#TextView_fontFamily
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800171 * @attr ref android.R.styleable#TextView_typeface
172 * @attr ref android.R.styleable#TextView_textStyle
173 * @attr ref android.R.styleable#TextView_cursorVisible
174 * @attr ref android.R.styleable#TextView_maxLines
175 * @attr ref android.R.styleable#TextView_maxHeight
176 * @attr ref android.R.styleable#TextView_lines
177 * @attr ref android.R.styleable#TextView_height
178 * @attr ref android.R.styleable#TextView_minLines
179 * @attr ref android.R.styleable#TextView_minHeight
180 * @attr ref android.R.styleable#TextView_maxEms
181 * @attr ref android.R.styleable#TextView_maxWidth
182 * @attr ref android.R.styleable#TextView_ems
183 * @attr ref android.R.styleable#TextView_width
184 * @attr ref android.R.styleable#TextView_minEms
185 * @attr ref android.R.styleable#TextView_minWidth
186 * @attr ref android.R.styleable#TextView_gravity
187 * @attr ref android.R.styleable#TextView_scrollHorizontally
188 * @attr ref android.R.styleable#TextView_password
189 * @attr ref android.R.styleable#TextView_singleLine
190 * @attr ref android.R.styleable#TextView_selectAllOnFocus
191 * @attr ref android.R.styleable#TextView_includeFontPadding
192 * @attr ref android.R.styleable#TextView_maxLength
193 * @attr ref android.R.styleable#TextView_shadowColor
194 * @attr ref android.R.styleable#TextView_shadowDx
195 * @attr ref android.R.styleable#TextView_shadowDy
196 * @attr ref android.R.styleable#TextView_shadowRadius
197 * @attr ref android.R.styleable#TextView_autoLink
198 * @attr ref android.R.styleable#TextView_linksClickable
199 * @attr ref android.R.styleable#TextView_numeric
200 * @attr ref android.R.styleable#TextView_digits
201 * @attr ref android.R.styleable#TextView_phoneNumber
202 * @attr ref android.R.styleable#TextView_inputMethod
203 * @attr ref android.R.styleable#TextView_capitalize
204 * @attr ref android.R.styleable#TextView_autoText
205 * @attr ref android.R.styleable#TextView_editable
Romain Guyd6a463a2009-05-21 23:10:10 -0700206 * @attr ref android.R.styleable#TextView_freezesText
207 * @attr ref android.R.styleable#TextView_ellipsize
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800208 * @attr ref android.R.styleable#TextView_drawableTop
209 * @attr ref android.R.styleable#TextView_drawableBottom
210 * @attr ref android.R.styleable#TextView_drawableRight
211 * @attr ref android.R.styleable#TextView_drawableLeft
Fabrice Di Megliod1591092012-03-07 15:34:38 -0800212 * @attr ref android.R.styleable#TextView_drawableStart
213 * @attr ref android.R.styleable#TextView_drawableEnd
Romain Guyd6a463a2009-05-21 23:10:10 -0700214 * @attr ref android.R.styleable#TextView_drawablePadding
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800215 * @attr ref android.R.styleable#TextView_lineSpacingExtra
216 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
217 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
Romain Guyd6a463a2009-05-21 23:10:10 -0700218 * @attr ref android.R.styleable#TextView_inputType
219 * @attr ref android.R.styleable#TextView_imeOptions
220 * @attr ref android.R.styleable#TextView_privateImeOptions
221 * @attr ref android.R.styleable#TextView_imeActionLabel
222 * @attr ref android.R.styleable#TextView_imeActionId
223 * @attr ref android.R.styleable#TextView_editorExtras
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800224 */
225@RemoteView
226public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -0700227 static final String LOG_TAG = "TextView";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800228 static final boolean DEBUG_EXTRACT = false;
Gilles Debunneb7012e842011-02-24 15:40:38 -0800229
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800230 // Enum for the "typeface" XML parameter.
231 // TODO: How can we get this from the XML instead of hardcoding it here?
232 private static final int SANS = 1;
233 private static final int SERIF = 2;
234 private static final int MONOSPACE = 3;
235
236 // Bitfield for the "numeric" XML parameter.
237 // TODO: How can we get this from the XML instead of hardcoding it here?
238 private static final int SIGNED = 2;
239 private static final int DECIMAL = 4;
240
Adam Powell282e3772011-08-30 16:51:11 -0700241 /**
242 * Draw marquee text with fading edges as usual
243 */
244 private static final int MARQUEE_FADE_NORMAL = 0;
245
246 /**
247 * Draw marquee text as ellipsize end while inactive instead of with the fade.
248 * (Useful for devices where the fade can be expensive if overdone)
249 */
250 private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1;
251
252 /**
253 * Draw marquee text with fading edges because it is currently active/animating.
254 */
255 private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2;
256
Gilles Debunne60e21862012-01-30 15:04:14 -0800257 private static final int LINES = 1;
258 private static final int EMS = LINES;
259 private static final int PIXELS = 2;
260
261 private static final RectF TEMP_RECTF = new RectF();
Gilles Debunne60e21862012-01-30 15:04:14 -0800262
263 // XXX should be much larger
264 private static final int VERY_WIDE = 1024*1024;
Gilles Debunne60e21862012-01-30 15:04:14 -0800265 private static final int ANIMATED_SCROLL_GAP = 250;
266
267 private static final InputFilter[] NO_FILTERS = new InputFilter[0];
268 private static final Spanned EMPTY_SPANNED = new SpannedString("");
269
Gilles Debunne60e21862012-01-30 15:04:14 -0800270 private static final int CHANGE_WATCHER_PRIORITY = 100;
271
272 // New state used to change background based on whether this TextView is multiline.
273 private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
274
275 // System wide time for last cut or copy action.
Gilles Debunned88876a2012-03-16 17:34:04 -0700276 static long LAST_CUT_OR_COPY_TIME;
Gilles Debunne60e21862012-01-30 15:04:14 -0800277
Gilles Debunne60e21862012-01-30 15:04:14 -0800278 private ColorStateList mTextColor;
279 private ColorStateList mHintTextColor;
280 private ColorStateList mLinkTextColor;
281 private int mCurTextColor;
282 private int mCurHintTextColor;
283 private boolean mFreezesText;
284 private boolean mTemporaryDetach;
285 private boolean mDispatchTemporaryDetach;
286
287 private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
288 private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
289
290 private float mShadowRadius, mShadowDx, mShadowDy;
291
292 private boolean mPreDrawRegistered;
293
294 private TextUtils.TruncateAt mEllipsize;
295
296 static class Drawables {
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800297 final static int DRAWABLE_NONE = -1;
298 final static int DRAWABLE_RIGHT = 0;
299 final static int DRAWABLE_LEFT = 1;
300
Gilles Debunne60e21862012-01-30 15:04:14 -0800301 final Rect mCompoundRect = new Rect();
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800302
Gilles Debunne60e21862012-01-30 15:04:14 -0800303 Drawable mDrawableTop, mDrawableBottom, mDrawableLeft, mDrawableRight,
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800304 mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp;
305
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700306 Drawable mDrawableLeftInitial, mDrawableRightInitial;
307 boolean mIsRtlCompatibilityMode;
308 boolean mOverride;
309
Gilles Debunne60e21862012-01-30 15:04:14 -0800310 int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight,
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800311 mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp;
312
Gilles Debunne60e21862012-01-30 15:04:14 -0800313 int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight,
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800314 mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp;
315
Gilles Debunne60e21862012-01-30 15:04:14 -0800316 int mDrawablePadding;
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800317
318 int mDrawableSaved = DRAWABLE_NONE;
319
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700320 public Drawables(Context context) {
321 final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
322 mIsRtlCompatibilityMode = (targetSdkVersion < JELLY_BEAN_MR1 ||
323 !context.getApplicationInfo().hasRtlSupport());
324 mOverride = false;
325 }
326
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800327 public void resolveWithLayoutDirection(int layoutDirection) {
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700328 // First reset "left" and "right" drawables to their initial values
329 mDrawableLeft = mDrawableLeftInitial;
330 mDrawableRight = mDrawableRightInitial;
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800331
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700332 if (mIsRtlCompatibilityMode) {
333 // Use "start" drawable as "left" drawable if the "left" drawable was not defined
334 if (mDrawableStart != null && mDrawableLeft == null) {
335 mDrawableLeft = mDrawableStart;
336 mDrawableSizeLeft = mDrawableSizeStart;
337 mDrawableHeightLeft = mDrawableHeightStart;
338 }
339 // Use "end" drawable as "right" drawable if the "right" drawable was not defined
340 if (mDrawableEnd != null && mDrawableRight == null) {
341 mDrawableRight = mDrawableEnd;
342 mDrawableSizeRight = mDrawableSizeEnd;
343 mDrawableHeightRight = mDrawableHeightEnd;
344 }
345 } else {
346 // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right"
347 // drawable if and only if they have been defined
348 switch(layoutDirection) {
349 case LAYOUT_DIRECTION_RTL:
350 if (mOverride) {
351 mDrawableRight = mDrawableStart;
352 mDrawableSizeRight = mDrawableSizeStart;
353 mDrawableHeightRight = mDrawableHeightStart;
354 }
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800355
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700356 if (mOverride) {
357 mDrawableLeft = mDrawableEnd;
358 mDrawableSizeLeft = mDrawableSizeEnd;
359 mDrawableHeightLeft = mDrawableHeightEnd;
360 }
361 break;
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800362
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700363 case LAYOUT_DIRECTION_LTR:
364 default:
365 if (mOverride) {
366 mDrawableLeft = mDrawableStart;
367 mDrawableSizeLeft = mDrawableSizeStart;
368 mDrawableHeightLeft = mDrawableHeightStart;
369 }
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800370
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700371 if (mOverride) {
372 mDrawableRight = mDrawableEnd;
373 mDrawableSizeRight = mDrawableSizeEnd;
374 mDrawableHeightRight = mDrawableHeightEnd;
375 }
376 break;
377 }
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800378 }
379 applyErrorDrawableIfNeeded(layoutDirection);
380 updateDrawablesLayoutDirection(layoutDirection);
381 }
382
383 private void updateDrawablesLayoutDirection(int layoutDirection) {
384 if (mDrawableLeft != null) {
385 mDrawableLeft.setLayoutDirection(layoutDirection);
386 }
387 if (mDrawableRight != null) {
388 mDrawableRight.setLayoutDirection(layoutDirection);
389 }
390 if (mDrawableTop != null) {
391 mDrawableTop.setLayoutDirection(layoutDirection);
392 }
393 if (mDrawableBottom != null) {
394 mDrawableBottom.setLayoutDirection(layoutDirection);
395 }
396 }
397
398 public void setErrorDrawable(Drawable dr, TextView tv) {
399 if (mDrawableError != dr && mDrawableError != null) {
400 mDrawableError.setCallback(null);
401 }
402 mDrawableError = dr;
403
404 final Rect compoundRect = mCompoundRect;
405 int[] state = tv.getDrawableState();
406
407 if (mDrawableError != null) {
408 mDrawableError.setState(state);
409 mDrawableError.copyBounds(compoundRect);
410 mDrawableError.setCallback(tv);
411 mDrawableSizeError = compoundRect.width();
412 mDrawableHeightError = compoundRect.height();
413 } else {
414 mDrawableSizeError = mDrawableHeightError = 0;
415 }
416 }
417
418 private void applyErrorDrawableIfNeeded(int layoutDirection) {
419 // first restore the initial state if needed
420 switch (mDrawableSaved) {
421 case DRAWABLE_LEFT:
422 mDrawableLeft = mDrawableTemp;
423 mDrawableSizeLeft = mDrawableSizeTemp;
424 mDrawableHeightLeft = mDrawableHeightTemp;
425 break;
426 case DRAWABLE_RIGHT:
427 mDrawableRight = mDrawableTemp;
428 mDrawableSizeRight = mDrawableSizeTemp;
429 mDrawableHeightRight = mDrawableHeightTemp;
430 break;
431 case DRAWABLE_NONE:
432 default:
433 }
434 // then, if needed, assign the Error drawable to the correct location
435 if (mDrawableError != null) {
436 switch(layoutDirection) {
437 case LAYOUT_DIRECTION_RTL:
438 mDrawableSaved = DRAWABLE_LEFT;
439
440 mDrawableTemp = mDrawableLeft;
441 mDrawableSizeTemp = mDrawableSizeLeft;
442 mDrawableHeightTemp = mDrawableHeightLeft;
443
444 mDrawableLeft = mDrawableError;
445 mDrawableSizeLeft = mDrawableSizeError;
446 mDrawableHeightLeft = mDrawableHeightError;
447 break;
448 case LAYOUT_DIRECTION_LTR:
449 default:
450 mDrawableSaved = DRAWABLE_RIGHT;
451
452 mDrawableTemp = mDrawableRight;
453 mDrawableSizeTemp = mDrawableSizeRight;
454 mDrawableHeightTemp = mDrawableHeightRight;
455
456 mDrawableRight = mDrawableError;
457 mDrawableSizeRight = mDrawableSizeError;
458 mDrawableHeightRight = mDrawableHeightError;
459 break;
460 }
461 }
462 }
Gilles Debunne60e21862012-01-30 15:04:14 -0800463 }
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800464
Gilles Debunned88876a2012-03-16 17:34:04 -0700465 Drawables mDrawables;
Gilles Debunne60e21862012-01-30 15:04:14 -0800466
467 private CharWrapper mCharWrapper;
468
469 private Marquee mMarquee;
470 private boolean mRestartMarquee;
471
472 private int mMarqueeRepeatLimit = 3;
473
Fabrice Di Meglio1957d282012-10-25 17:42:39 -0700474 private int mLastLayoutDirection = -1;
Gilles Debunne60e21862012-01-30 15:04:14 -0800475
476 /**
477 * On some devices the fading edges add a performance penalty if used
478 * extensively in the same layout. This mode indicates how the marquee
479 * is currently being shown, if applicable. (mEllipsize will == MARQUEE)
480 */
481 private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
482
483 /**
484 * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores
485 * the layout that should be used when the mode switches.
486 */
487 private Layout mSavedMarqueeModeLayout;
488
489 @ViewDebug.ExportedProperty(category = "text")
490 private CharSequence mText;
491 private CharSequence mTransformed;
492 private BufferType mBufferType = BufferType.NORMAL;
493
494 private CharSequence mHint;
495 private Layout mHintLayout;
496
497 private MovementMethod mMovement;
498
499 private TransformationMethod mTransformation;
500 private boolean mAllowTransformationLengthChange;
501 private ChangeWatcher mChangeWatcher;
502
503 private ArrayList<TextWatcher> mListeners;
504
505 // display attributes
506 private final TextPaint mTextPaint;
507 private boolean mUserSetTextScaleX;
508 private Layout mLayout;
509
510 private int mGravity = Gravity.TOP | Gravity.START;
511 private boolean mHorizontallyScrolling;
512
513 private int mAutoLinkMask;
514 private boolean mLinksClickable = true;
515
516 private float mSpacingMult = 1.0f;
517 private float mSpacingAdd = 0.0f;
518
519 private int mMaximum = Integer.MAX_VALUE;
520 private int mMaxMode = LINES;
521 private int mMinimum = 0;
522 private int mMinMode = LINES;
523
524 private int mOldMaximum = mMaximum;
525 private int mOldMaxMode = mMaxMode;
526
527 private int mMaxWidth = Integer.MAX_VALUE;
528 private int mMaxWidthMode = PIXELS;
529 private int mMinWidth = 0;
530 private int mMinWidthMode = PIXELS;
531
532 private boolean mSingleLine;
533 private int mDesiredHeightAtMeasure = -1;
534 private boolean mIncludePad = true;
Raph Levienf5c1a872012-10-15 17:22:26 -0700535 private int mDeferScroll = -1;
Gilles Debunne60e21862012-01-30 15:04:14 -0800536
537 // tmp primitives, so we don't alloc them on each draw
538 private Rect mTempRect;
539 private long mLastScroll;
540 private Scroller mScroller;
541
542 private BoringLayout.Metrics mBoring, mHintBoring;
543 private BoringLayout mSavedLayout, mSavedHintLayout;
544
545 private TextDirectionHeuristic mTextDir;
546
547 private InputFilter[] mFilters = NO_FILTERS;
548
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +0900549 private volatile Locale mCurrentSpellCheckerLocaleCache;
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +0900550 private final ReentrantLock mCurrentTextServicesLocaleLock = new ReentrantLock();
551
Gilles Debunne83051b82012-02-24 20:01:13 -0800552 // It is possible to have a selection even when mEditor is null (programmatically set, like when
553 // a link is pressed). These highlight-related fields do not go in mEditor.
Gilles Debunned88876a2012-03-16 17:34:04 -0700554 int mHighlightColor = 0x6633B5E5;
Gilles Debunne83051b82012-02-24 20:01:13 -0800555 private Path mHighlightPath;
556 private final Paint mHighlightPaint;
557 private boolean mHighlightPathBogus = true;
558
Gilles Debunne60e21862012-01-30 15:04:14 -0800559 // Although these fields are specific to editable text, they are not added to Editor because
560 // they are defined by the TextView's style and are theme-dependent.
Gilles Debunned88876a2012-03-16 17:34:04 -0700561 int mCursorDrawableRes;
Gilles Debunne60e21862012-01-30 15:04:14 -0800562 // These four fields, could be moved to Editor, since we know their default values and we
563 // could condition the creation of the Editor to a non standard value. This is however
564 // brittle since the hardcoded values here (such as
565 // com.android.internal.R.drawable.text_select_handle_left) would have to be updated if the
566 // default style is modified.
Gilles Debunned88876a2012-03-16 17:34:04 -0700567 int mTextSelectHandleLeftRes;
568 int mTextSelectHandleRightRes;
569 int mTextSelectHandleRes;
570 int mTextEditSuggestionItemLayout;
Gilles Debunne60e21862012-01-30 15:04:14 -0800571
572 /**
573 * EditText specific data, created on demand when one of the Editor fields is used.
Gilles Debunne5fae9962012-05-08 14:53:20 -0700574 * See {@link #createEditorIfNeeded()}.
Gilles Debunne60e21862012-01-30 15:04:14 -0800575 */
576 private Editor mEditor;
577
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800578 /*
579 * Kick-start the font cache for the zygote process (to pay the cost of
580 * initializing freetype for our default font only once).
581 */
582 static {
583 Paint p = new Paint();
584 p.setAntiAlias(true);
585 // We don't care about the result, just the side-effect of measuring.
586 p.measureText("H");
587 }
588
589 /**
590 * Interface definition for a callback to be invoked when an action is
591 * performed on the editor.
592 */
593 public interface OnEditorActionListener {
594 /**
595 * Called when an action is being performed.
596 *
597 * @param v The view that was clicked.
598 * @param actionId Identifier of the action. This will be either the
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700599 * identifier you supplied, or {@link EditorInfo#IME_NULL
600 * EditorInfo.IME_NULL} if being called due to the enter key
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800601 * being pressed.
602 * @param event If triggered by an enter key, this is the event;
603 * otherwise, this is null.
604 * @return Return true if you have consumed the action, else false.
605 */
606 boolean onEditorAction(TextView v, int actionId, KeyEvent event);
607 }
Gilles Debunne21078e42011-08-02 10:22:35 -0700608
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800609 public TextView(Context context) {
610 this(context, null);
611 }
612
Gilles Debunnec1714022012-01-17 13:59:23 -0800613 public TextView(Context context, AttributeSet attrs) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800614 this(context, attrs, com.android.internal.R.attr.textViewStyle);
615 }
616
Gilles Debunnee15b3582010-06-16 15:17:21 -0700617 @SuppressWarnings("deprecation")
Gilles Debunnec1714022012-01-17 13:59:23 -0800618 public TextView(Context context, AttributeSet attrs, int defStyle) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800619 super(context, attrs, defStyle);
620 mText = "";
621
Christopher Tate1373a8e2011-11-10 19:59:13 -0800622 final Resources res = getResources();
623 final CompatibilityInfo compat = res.getCompatibilityInfo();
624
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800625 mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
Christopher Tate1373a8e2011-11-10 19:59:13 -0800626 mTextPaint.density = res.getDisplayMetrics().density;
627 mTextPaint.setCompatibilityScaling(compat.applicationScale);
Gilles Debunne8cbb4c62011-01-24 12:33:56 -0800628
Gilles Debunne83051b82012-02-24 20:01:13 -0800629 mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
630 mHighlightPaint.setCompatibilityScaling(compat.applicationScale);
631
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800632 mMovement = getDefaultMovementMethod();
Gilles Debunne60e21862012-01-30 15:04:14 -0800633
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800634 mTransformation = null;
635
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800636 int textColorHighlight = 0;
637 ColorStateList textColor = null;
638 ColorStateList textColorHint = null;
639 ColorStateList textColorLink = null;
640 int textSize = 15;
Raph Leviend570e892012-05-09 11:45:34 -0700641 String fontFamily = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800642 int typefaceIndex = -1;
643 int styleIndex = -1;
Adam Powell7f8f79a2011-07-07 18:35:54 -0700644 boolean allCaps = false;
Adam Powellac91df82013-02-14 13:48:47 -0800645 int shadowcolor = 0;
646 float dx = 0, dy = 0, r = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800647
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700648 final Resources.Theme theme = context.getTheme();
649
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800650 /*
651 * Look the appearance up without checking first if it exists because
652 * almost every TextView has one and it greatly simplifies the logic
653 * to be able to parse the appearance first and then let specific tags
654 * for this View override it.
655 */
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700656 TypedArray a = theme.obtainStyledAttributes(
657 attrs, com.android.internal.R.styleable.TextViewAppearance, defStyle, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800658 TypedArray appearance = null;
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700659 int ap = a.getResourceId(
660 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);
661 a.recycle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800662 if (ap != -1) {
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700663 appearance = theme.obtainStyledAttributes(
664 ap, com.android.internal.R.styleable.TextAppearance);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800665 }
666 if (appearance != null) {
667 int n = appearance.getIndexCount();
668 for (int i = 0; i < n; i++) {
669 int attr = appearance.getIndex(i);
670
671 switch (attr) {
672 case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
673 textColorHighlight = appearance.getColor(attr, textColorHighlight);
674 break;
675
676 case com.android.internal.R.styleable.TextAppearance_textColor:
677 textColor = appearance.getColorStateList(attr);
678 break;
679
680 case com.android.internal.R.styleable.TextAppearance_textColorHint:
681 textColorHint = appearance.getColorStateList(attr);
682 break;
683
684 case com.android.internal.R.styleable.TextAppearance_textColorLink:
685 textColorLink = appearance.getColorStateList(attr);
686 break;
687
688 case com.android.internal.R.styleable.TextAppearance_textSize:
689 textSize = appearance.getDimensionPixelSize(attr, textSize);
690 break;
691
692 case com.android.internal.R.styleable.TextAppearance_typeface:
693 typefaceIndex = appearance.getInt(attr, -1);
694 break;
695
Raph Leviend570e892012-05-09 11:45:34 -0700696 case com.android.internal.R.styleable.TextAppearance_fontFamily:
697 fontFamily = appearance.getString(attr);
698 break;
699
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800700 case com.android.internal.R.styleable.TextAppearance_textStyle:
701 styleIndex = appearance.getInt(attr, -1);
702 break;
Adam Powell7f8f79a2011-07-07 18:35:54 -0700703
704 case com.android.internal.R.styleable.TextAppearance_textAllCaps:
705 allCaps = appearance.getBoolean(attr, false);
706 break;
Adam Powellac91df82013-02-14 13:48:47 -0800707
708 case com.android.internal.R.styleable.TextAppearance_shadowColor:
709 shadowcolor = a.getInt(attr, 0);
710 break;
711
712 case com.android.internal.R.styleable.TextAppearance_shadowDx:
713 dx = a.getFloat(attr, 0);
714 break;
715
716 case com.android.internal.R.styleable.TextAppearance_shadowDy:
717 dy = a.getFloat(attr, 0);
718 break;
719
720 case com.android.internal.R.styleable.TextAppearance_shadowRadius:
721 r = a.getFloat(attr, 0);
722 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800723 }
724 }
725
726 appearance.recycle();
727 }
728
729 boolean editable = getDefaultEditable();
730 CharSequence inputMethod = null;
731 int numeric = 0;
732 CharSequence digits = null;
733 boolean phone = false;
734 boolean autotext = false;
735 int autocap = -1;
736 int buffertype = 0;
737 boolean selectallonfocus = false;
738 Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -0700739 drawableBottom = null, drawableStart = null, drawableEnd = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800740 int drawablePadding = 0;
741 int ellipsize = -1;
Gilles Debunnef95449d2010-11-05 13:54:13 -0700742 boolean singleLine = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800743 int maxlength = -1;
744 CharSequence text = "";
Romain Guy4dc4f732009-06-19 15:16:40 -0700745 CharSequence hint = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800746 boolean password = false;
747 int inputType = EditorInfo.TYPE_NULL;
748
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700749 a = theme.obtainStyledAttributes(
750 attrs, com.android.internal.R.styleable.TextView, defStyle, 0);
751
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800752 int n = a.getIndexCount();
753 for (int i = 0; i < n; i++) {
754 int attr = a.getIndex(i);
755
756 switch (attr) {
757 case com.android.internal.R.styleable.TextView_editable:
758 editable = a.getBoolean(attr, editable);
759 break;
760
761 case com.android.internal.R.styleable.TextView_inputMethod:
762 inputMethod = a.getText(attr);
763 break;
764
765 case com.android.internal.R.styleable.TextView_numeric:
766 numeric = a.getInt(attr, numeric);
767 break;
768
769 case com.android.internal.R.styleable.TextView_digits:
770 digits = a.getText(attr);
771 break;
772
773 case com.android.internal.R.styleable.TextView_phoneNumber:
774 phone = a.getBoolean(attr, phone);
775 break;
776
777 case com.android.internal.R.styleable.TextView_autoText:
778 autotext = a.getBoolean(attr, autotext);
779 break;
780
781 case com.android.internal.R.styleable.TextView_capitalize:
782 autocap = a.getInt(attr, autocap);
783 break;
784
785 case com.android.internal.R.styleable.TextView_bufferType:
786 buffertype = a.getInt(attr, buffertype);
787 break;
788
789 case com.android.internal.R.styleable.TextView_selectAllOnFocus:
790 selectallonfocus = a.getBoolean(attr, selectallonfocus);
791 break;
792
793 case com.android.internal.R.styleable.TextView_autoLink:
794 mAutoLinkMask = a.getInt(attr, 0);
795 break;
796
797 case com.android.internal.R.styleable.TextView_linksClickable:
798 mLinksClickable = a.getBoolean(attr, true);
799 break;
800
801 case com.android.internal.R.styleable.TextView_drawableLeft:
802 drawableLeft = a.getDrawable(attr);
803 break;
804
805 case com.android.internal.R.styleable.TextView_drawableTop:
806 drawableTop = a.getDrawable(attr);
807 break;
808
809 case com.android.internal.R.styleable.TextView_drawableRight:
810 drawableRight = a.getDrawable(attr);
811 break;
812
813 case com.android.internal.R.styleable.TextView_drawableBottom:
814 drawableBottom = a.getDrawable(attr);
815 break;
816
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -0700817 case com.android.internal.R.styleable.TextView_drawableStart:
818 drawableStart = a.getDrawable(attr);
819 break;
820
821 case com.android.internal.R.styleable.TextView_drawableEnd:
822 drawableEnd = a.getDrawable(attr);
823 break;
824
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800825 case com.android.internal.R.styleable.TextView_drawablePadding:
826 drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
827 break;
828
829 case com.android.internal.R.styleable.TextView_maxLines:
830 setMaxLines(a.getInt(attr, -1));
831 break;
832
833 case com.android.internal.R.styleable.TextView_maxHeight:
834 setMaxHeight(a.getDimensionPixelSize(attr, -1));
835 break;
836
837 case com.android.internal.R.styleable.TextView_lines:
838 setLines(a.getInt(attr, -1));
839 break;
840
841 case com.android.internal.R.styleable.TextView_height:
842 setHeight(a.getDimensionPixelSize(attr, -1));
843 break;
844
845 case com.android.internal.R.styleable.TextView_minLines:
846 setMinLines(a.getInt(attr, -1));
847 break;
848
849 case com.android.internal.R.styleable.TextView_minHeight:
850 setMinHeight(a.getDimensionPixelSize(attr, -1));
851 break;
852
853 case com.android.internal.R.styleable.TextView_maxEms:
854 setMaxEms(a.getInt(attr, -1));
855 break;
856
857 case com.android.internal.R.styleable.TextView_maxWidth:
858 setMaxWidth(a.getDimensionPixelSize(attr, -1));
859 break;
860
861 case com.android.internal.R.styleable.TextView_ems:
862 setEms(a.getInt(attr, -1));
863 break;
864
865 case com.android.internal.R.styleable.TextView_width:
866 setWidth(a.getDimensionPixelSize(attr, -1));
867 break;
868
869 case com.android.internal.R.styleable.TextView_minEms:
870 setMinEms(a.getInt(attr, -1));
871 break;
872
873 case com.android.internal.R.styleable.TextView_minWidth:
874 setMinWidth(a.getDimensionPixelSize(attr, -1));
875 break;
876
877 case com.android.internal.R.styleable.TextView_gravity:
878 setGravity(a.getInt(attr, -1));
879 break;
880
881 case com.android.internal.R.styleable.TextView_hint:
Romain Guy4dc4f732009-06-19 15:16:40 -0700882 hint = a.getText(attr);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800883 break;
884
885 case com.android.internal.R.styleable.TextView_text:
886 text = a.getText(attr);
887 break;
888
889 case com.android.internal.R.styleable.TextView_scrollHorizontally:
890 if (a.getBoolean(attr, false)) {
891 setHorizontallyScrolling(true);
892 }
893 break;
894
895 case com.android.internal.R.styleable.TextView_singleLine:
896 singleLine = a.getBoolean(attr, singleLine);
897 break;
898
899 case com.android.internal.R.styleable.TextView_ellipsize:
900 ellipsize = a.getInt(attr, ellipsize);
901 break;
902
903 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
904 setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
905 break;
906
907 case com.android.internal.R.styleable.TextView_includeFontPadding:
908 if (!a.getBoolean(attr, true)) {
909 setIncludeFontPadding(false);
910 }
911 break;
912
913 case com.android.internal.R.styleable.TextView_cursorVisible:
914 if (!a.getBoolean(attr, true)) {
915 setCursorVisible(false);
916 }
917 break;
918
919 case com.android.internal.R.styleable.TextView_maxLength:
920 maxlength = a.getInt(attr, -1);
921 break;
922
923 case com.android.internal.R.styleable.TextView_textScaleX:
924 setTextScaleX(a.getFloat(attr, 1.0f));
925 break;
926
927 case com.android.internal.R.styleable.TextView_freezesText:
928 mFreezesText = a.getBoolean(attr, false);
929 break;
930
931 case com.android.internal.R.styleable.TextView_shadowColor:
932 shadowcolor = a.getInt(attr, 0);
933 break;
934
935 case com.android.internal.R.styleable.TextView_shadowDx:
936 dx = a.getFloat(attr, 0);
937 break;
938
939 case com.android.internal.R.styleable.TextView_shadowDy:
940 dy = a.getFloat(attr, 0);
941 break;
942
943 case com.android.internal.R.styleable.TextView_shadowRadius:
944 r = a.getFloat(attr, 0);
945 break;
946
947 case com.android.internal.R.styleable.TextView_enabled:
948 setEnabled(a.getBoolean(attr, isEnabled()));
949 break;
950
951 case com.android.internal.R.styleable.TextView_textColorHighlight:
952 textColorHighlight = a.getColor(attr, textColorHighlight);
953 break;
954
955 case com.android.internal.R.styleable.TextView_textColor:
956 textColor = a.getColorStateList(attr);
957 break;
958
959 case com.android.internal.R.styleable.TextView_textColorHint:
960 textColorHint = a.getColorStateList(attr);
961 break;
962
963 case com.android.internal.R.styleable.TextView_textColorLink:
964 textColorLink = a.getColorStateList(attr);
965 break;
966
967 case com.android.internal.R.styleable.TextView_textSize:
968 textSize = a.getDimensionPixelSize(attr, textSize);
969 break;
970
971 case com.android.internal.R.styleable.TextView_typeface:
972 typefaceIndex = a.getInt(attr, typefaceIndex);
973 break;
974
975 case com.android.internal.R.styleable.TextView_textStyle:
976 styleIndex = a.getInt(attr, styleIndex);
977 break;
978
Raph Leviend570e892012-05-09 11:45:34 -0700979 case com.android.internal.R.styleable.TextView_fontFamily:
980 fontFamily = a.getString(attr);
981 break;
982
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800983 case com.android.internal.R.styleable.TextView_password:
984 password = a.getBoolean(attr, password);
985 break;
986
987 case com.android.internal.R.styleable.TextView_lineSpacingExtra:
988 mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
989 break;
990
991 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
992 mSpacingMult = a.getFloat(attr, mSpacingMult);
993 break;
994
995 case com.android.internal.R.styleable.TextView_inputType:
Gilles Debunne60e21862012-01-30 15:04:14 -0800996 inputType = a.getInt(attr, EditorInfo.TYPE_NULL);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800997 break;
998
999 case com.android.internal.R.styleable.TextView_imeOptions:
Gilles Debunne5fae9962012-05-08 14:53:20 -07001000 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001001 mEditor.createInputContentTypeIfNeeded();
1002 mEditor.mInputContentType.imeOptions = a.getInt(attr,
1003 mEditor.mInputContentType.imeOptions);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001004 break;
1005
1006 case com.android.internal.R.styleable.TextView_imeActionLabel:
Gilles Debunne5fae9962012-05-08 14:53:20 -07001007 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001008 mEditor.createInputContentTypeIfNeeded();
1009 mEditor.mInputContentType.imeActionLabel = a.getText(attr);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001010 break;
1011
1012 case com.android.internal.R.styleable.TextView_imeActionId:
Gilles Debunne5fae9962012-05-08 14:53:20 -07001013 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001014 mEditor.createInputContentTypeIfNeeded();
1015 mEditor.mInputContentType.imeActionId = a.getInt(attr,
1016 mEditor.mInputContentType.imeActionId);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001017 break;
1018
1019 case com.android.internal.R.styleable.TextView_privateImeOptions:
1020 setPrivateImeOptions(a.getString(attr));
1021 break;
1022
1023 case com.android.internal.R.styleable.TextView_editorExtras:
1024 try {
1025 setInputExtras(a.getResourceId(attr, 0));
1026 } catch (XmlPullParserException e) {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07001027 Log.w(LOG_TAG, "Failure reading input extras", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001028 } catch (IOException e) {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07001029 Log.w(LOG_TAG, "Failure reading input extras", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001030 }
1031 break;
Adam Powellb08013c2010-09-16 16:28:11 -07001032
Gilles Debunnef75c97e2011-02-10 16:09:53 -08001033 case com.android.internal.R.styleable.TextView_textCursorDrawable:
1034 mCursorDrawableRes = a.getResourceId(attr, 0);
1035 break;
1036
Adam Powellb08013c2010-09-16 16:28:11 -07001037 case com.android.internal.R.styleable.TextView_textSelectHandleLeft:
1038 mTextSelectHandleLeftRes = a.getResourceId(attr, 0);
1039 break;
1040
1041 case com.android.internal.R.styleable.TextView_textSelectHandleRight:
1042 mTextSelectHandleRightRes = a.getResourceId(attr, 0);
1043 break;
1044
1045 case com.android.internal.R.styleable.TextView_textSelectHandle:
1046 mTextSelectHandleRes = a.getResourceId(attr, 0);
1047 break;
Gilles Debunne7b9652b2010-10-26 16:27:12 -07001048
Gilles Debunne69340442011-03-31 13:37:51 -07001049 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout:
1050 mTextEditSuggestionItemLayout = a.getResourceId(attr, 0);
1051 break;
1052
Gilles Debunne86b9c782010-11-11 10:43:48 -08001053 case com.android.internal.R.styleable.TextView_textIsSelectable:
Gilles Debunne60e21862012-01-30 15:04:14 -08001054 setTextIsSelectable(a.getBoolean(attr, false));
Gilles Debunne86b9c782010-11-11 10:43:48 -08001055 break;
Gilles Debunnef3a135b2011-05-23 16:28:47 -07001056
Adam Powell7f8f79a2011-07-07 18:35:54 -07001057 case com.android.internal.R.styleable.TextView_textAllCaps:
1058 allCaps = a.getBoolean(attr, false);
1059 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001060 }
1061 }
1062 a.recycle();
1063
1064 BufferType bufferType = BufferType.EDITABLE;
1065
Gilles Debunned7483bf2010-11-10 10:47:45 -08001066 final int variation =
1067 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
1068 final boolean passwordInputType = variation
1069 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
1070 final boolean webPasswordInputType = variation
1071 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD);
Ken Wakasa82d731a2010-12-24 23:42:41 +09001072 final boolean numberPasswordInputType = variation
1073 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
Gilles Debunned7483bf2010-11-10 10:47:45 -08001074
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001075 if (inputMethod != null) {
Gilles Debunnee15b3582010-06-16 15:17:21 -07001076 Class<?> c;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001077
1078 try {
1079 c = Class.forName(inputMethod.toString());
1080 } catch (ClassNotFoundException ex) {
1081 throw new RuntimeException(ex);
1082 }
1083
1084 try {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001085 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001086 mEditor.mKeyListener = (KeyListener) c.newInstance();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001087 } catch (InstantiationException ex) {
1088 throw new RuntimeException(ex);
1089 } catch (IllegalAccessException ex) {
1090 throw new RuntimeException(ex);
1091 }
1092 try {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001093 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001094 ? inputType
Gilles Debunne2d373a12012-04-20 15:32:19 -07001095 : mEditor.mKeyListener.getInputType();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001096 } catch (IncompatibleClassChangeError e) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001097 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001098 }
1099 } else if (digits != null) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001100 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001101 mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString());
Dianne Hackborn7ed6ee52009-09-10 18:41:28 -07001102 // If no input type was specified, we will default to generic
1103 // text, since we can't tell the IME about the set of digits
1104 // that was selected.
Gilles Debunne2d373a12012-04-20 15:32:19 -07001105 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
Dianne Hackborn7ed6ee52009-09-10 18:41:28 -07001106 ? inputType : EditorInfo.TYPE_CLASS_TEXT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001107 } else if (inputType != EditorInfo.TYPE_NULL) {
1108 setInputType(inputType, true);
Gilles Debunne91a08cf2010-11-08 17:34:49 -08001109 // If set, the input type overrides what was set using the deprecated singleLine flag.
1110 singleLine = !isMultilineInputType(inputType);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001111 } else if (phone) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001112 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001113 mEditor.mKeyListener = DialerKeyListener.getInstance();
1114 mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001115 } else if (numeric != 0) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001116 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001117 mEditor.mKeyListener = DigitsKeyListener.getInstance((numeric & SIGNED) != 0,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001118 (numeric & DECIMAL) != 0);
1119 inputType = EditorInfo.TYPE_CLASS_NUMBER;
1120 if ((numeric & SIGNED) != 0) {
1121 inputType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED;
1122 }
1123 if ((numeric & DECIMAL) != 0) {
1124 inputType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL;
1125 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07001126 mEditor.mInputType = inputType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001127 } else if (autotext || autocap != -1) {
1128 TextKeyListener.Capitalize cap;
1129
1130 inputType = EditorInfo.TYPE_CLASS_TEXT;
Gilles Debunnef95449d2010-11-05 13:54:13 -07001131
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001132 switch (autocap) {
1133 case 1:
1134 cap = TextKeyListener.Capitalize.SENTENCES;
1135 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
1136 break;
1137
1138 case 2:
1139 cap = TextKeyListener.Capitalize.WORDS;
1140 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
1141 break;
1142
1143 case 3:
1144 cap = TextKeyListener.Capitalize.CHARACTERS;
1145 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
1146 break;
1147
1148 default:
1149 cap = TextKeyListener.Capitalize.NONE;
1150 break;
1151 }
1152
Gilles Debunne5fae9962012-05-08 14:53:20 -07001153 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001154 mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap);
1155 mEditor.mInputType = inputType;
Gilles Debunne60e21862012-01-30 15:04:14 -08001156 } else if (isTextSelectable()) {
Gilles Debunne86b9c782010-11-11 10:43:48 -08001157 // Prevent text changes from keyboard.
Gilles Debunne60e21862012-01-30 15:04:14 -08001158 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001159 mEditor.mKeyListener = null;
1160 mEditor.mInputType = EditorInfo.TYPE_NULL;
Gilles Debunne60e21862012-01-30 15:04:14 -08001161 }
Gilles Debunne86b9c782010-11-11 10:43:48 -08001162 bufferType = BufferType.SPANNABLE;
Gilles Debunne86b9c782010-11-11 10:43:48 -08001163 // So that selection can be changed using arrow keys and touch is handled.
1164 setMovementMethod(ArrowKeyMovementMethod.getInstance());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001165 } else if (editable) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001166 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001167 mEditor.mKeyListener = TextKeyListener.getInstance();
1168 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001169 } else {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001170 if (mEditor != null) mEditor.mKeyListener = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001171
1172 switch (buffertype) {
1173 case 0:
1174 bufferType = BufferType.NORMAL;
1175 break;
1176 case 1:
1177 bufferType = BufferType.SPANNABLE;
1178 break;
1179 case 2:
1180 bufferType = BufferType.EDITABLE;
1181 break;
1182 }
1183 }
1184
Gilles Debunne2d373a12012-04-20 15:32:19 -07001185 if (mEditor != null) mEditor.adjustInputType(password, passwordInputType,
1186 webPasswordInputType, numberPasswordInputType);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001187
1188 if (selectallonfocus) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001189 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001190 mEditor.mSelectAllOnFocus = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001191
1192 if (bufferType == BufferType.NORMAL)
1193 bufferType = BufferType.SPANNABLE;
1194 }
1195
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07001196 // This call will save the initial left/right drawables
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001197 setCompoundDrawablesWithIntrinsicBounds(
1198 drawableLeft, drawableTop, drawableRight, drawableBottom);
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001199 setRelativeDrawablesIfNeeded(drawableStart, drawableEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001200 setCompoundDrawablePadding(drawablePadding);
1201
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08001202 // Same as setSingleLine(), but make sure the transformation method and the maximum number
Gilles Debunne066460f2010-12-15 17:31:51 -08001203 // of lines of height are unchanged for multi-line TextViews.
Gilles Debunned7483bf2010-11-10 10:47:45 -08001204 setInputTypeSingleLine(singleLine);
Gilles Debunne066460f2010-12-15 17:31:51 -08001205 applySingleLine(singleLine, singleLine, singleLine);
Gilles Debunned7483bf2010-11-10 10:47:45 -08001206
Gilles Debunne60e21862012-01-30 15:04:14 -08001207 if (singleLine && getKeyListener() == null && ellipsize < 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001208 ellipsize = 3; // END
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001209 }
1210
1211 switch (ellipsize) {
1212 case 1:
1213 setEllipsize(TextUtils.TruncateAt.START);
1214 break;
1215 case 2:
1216 setEllipsize(TextUtils.TruncateAt.MIDDLE);
1217 break;
1218 case 3:
1219 setEllipsize(TextUtils.TruncateAt.END);
1220 break;
1221 case 4:
Adam Powell282e3772011-08-30 16:51:11 -07001222 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) {
1223 setHorizontalFadingEdgeEnabled(true);
1224 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
1225 } else {
1226 setHorizontalFadingEdgeEnabled(false);
1227 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
1228 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001229 setEllipsize(TextUtils.TruncateAt.MARQUEE);
1230 break;
1231 }
1232
1233 setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000));
1234 setHintTextColor(textColorHint);
1235 setLinkTextColor(textColorLink);
1236 if (textColorHighlight != 0) {
1237 setHighlightColor(textColorHighlight);
1238 }
1239 setRawTextSize(textSize);
1240
Adam Powell7f8f79a2011-07-07 18:35:54 -07001241 if (allCaps) {
1242 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
1243 }
1244
Ken Wakasa82d731a2010-12-24 23:42:41 +09001245 if (password || passwordInputType || webPasswordInputType || numberPasswordInputType) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001246 setTransformationMethod(PasswordTransformationMethod.getInstance());
1247 typefaceIndex = MONOSPACE;
Gilles Debunne2d373a12012-04-20 15:32:19 -07001248 } else if (mEditor != null &&
1249 (mEditor.mInputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
Gilles Debunned7483bf2010-11-10 10:47:45 -08001250 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08001251 typefaceIndex = MONOSPACE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001252 }
1253
Raph Leviend570e892012-05-09 11:45:34 -07001254 setTypefaceFromAttrs(fontFamily, typefaceIndex, styleIndex);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001255
1256 if (shadowcolor != 0) {
1257 setShadowLayer(r, dx, dy, shadowcolor);
1258 }
1259
1260 if (maxlength >= 0) {
1261 setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
1262 } else {
1263 setFilters(NO_FILTERS);
1264 }
1265
1266 setText(text, bufferType);
Romain Guy4dc4f732009-06-19 15:16:40 -07001267 if (hint != null) setHint(hint);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001268
1269 /*
1270 * Views are not normally focusable unless specified to be.
1271 * However, TextViews that have input or movement methods *are*
1272 * focusable by default.
1273 */
1274 a = context.obtainStyledAttributes(attrs,
1275 com.android.internal.R.styleable.View,
1276 defStyle, 0);
1277
Gilles Debunne60e21862012-01-30 15:04:14 -08001278 boolean focusable = mMovement != null || getKeyListener() != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001279 boolean clickable = focusable;
1280 boolean longClickable = focusable;
1281
1282 n = a.getIndexCount();
1283 for (int i = 0; i < n; i++) {
1284 int attr = a.getIndex(i);
1285
1286 switch (attr) {
1287 case com.android.internal.R.styleable.View_focusable:
1288 focusable = a.getBoolean(attr, focusable);
1289 break;
1290
1291 case com.android.internal.R.styleable.View_clickable:
1292 clickable = a.getBoolean(attr, clickable);
1293 break;
1294
1295 case com.android.internal.R.styleable.View_longClickable:
1296 longClickable = a.getBoolean(attr, longClickable);
1297 break;
1298 }
1299 }
1300 a.recycle();
1301
1302 setFocusable(focusable);
1303 setClickable(clickable);
1304 setLongClickable(longClickable);
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07001305
Gilles Debunned88876a2012-03-16 17:34:04 -07001306 if (mEditor != null) mEditor.prepareCursorControllers();
Svetoslav Ganov42138042012-03-20 11:51:39 -07001307
1308 // If not explicitly specified this view is important for accessibility.
1309 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
1310 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
1311 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001312 }
1313
Raph Leviend570e892012-05-09 11:45:34 -07001314 private void setTypefaceFromAttrs(String familyName, int typefaceIndex, int styleIndex) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001315 Typeface tf = null;
Raph Leviend570e892012-05-09 11:45:34 -07001316 if (familyName != null) {
1317 tf = Typeface.create(familyName, styleIndex);
1318 if (tf != null) {
1319 setTypeface(tf);
1320 return;
1321 }
1322 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001323 switch (typefaceIndex) {
1324 case SANS:
1325 tf = Typeface.SANS_SERIF;
1326 break;
1327
1328 case SERIF:
1329 tf = Typeface.SERIF;
1330 break;
1331
1332 case MONOSPACE:
1333 tf = Typeface.MONOSPACE;
1334 break;
1335 }
1336
1337 setTypeface(tf, styleIndex);
1338 }
1339
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001340 private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) {
1341 boolean hasRelativeDrawables = (start != null) || (end != null);
1342 if (hasRelativeDrawables) {
1343 Drawables dr = mDrawables;
1344 if (dr == null) {
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07001345 mDrawables = dr = new Drawables(getContext());
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001346 }
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07001347 mDrawables.mOverride = true;
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001348 final Rect compoundRect = dr.mCompoundRect;
1349 int[] state = getDrawableState();
1350 if (start != null) {
1351 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
1352 start.setState(state);
1353 start.copyBounds(compoundRect);
1354 start.setCallback(this);
1355
1356 dr.mDrawableStart = start;
1357 dr.mDrawableSizeStart = compoundRect.width();
1358 dr.mDrawableHeightStart = compoundRect.height();
1359 } else {
1360 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
1361 }
1362 if (end != null) {
1363 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
1364 end.setState(state);
1365 end.copyBounds(compoundRect);
1366 end.setCallback(this);
1367
1368 dr.mDrawableEnd = end;
1369 dr.mDrawableSizeEnd = compoundRect.width();
1370 dr.mDrawableHeightEnd = compoundRect.height();
1371 } else {
1372 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
1373 }
1374 }
1375 }
1376
Janos Levai042856c2010-10-15 02:53:58 +03001377 @Override
1378 public void setEnabled(boolean enabled) {
1379 if (enabled == isEnabled()) {
1380 return;
1381 }
1382
1383 if (!enabled) {
1384 // Hide the soft input if the currently active TextView is disabled
1385 InputMethodManager imm = InputMethodManager.peekInstance();
1386 if (imm != null && imm.isActive(this)) {
1387 imm.hideSoftInputFromWindow(getWindowToken(), 0);
1388 }
1389 }
Gilles Debunne545c4d42011-11-29 10:37:15 -08001390
Janos Levai042856c2010-10-15 02:53:58 +03001391 super.setEnabled(enabled);
Gilles Debunne545c4d42011-11-29 10:37:15 -08001392
Dianne Hackbornbc823852011-09-18 17:19:50 -07001393 if (enabled) {
1394 // Make sure IME is updated with current editor info.
1395 InputMethodManager imm = InputMethodManager.peekInstance();
1396 if (imm != null) imm.restartInput(this);
1397 }
Mark Wagnerf8185112011-10-25 16:33:41 -07001398
Gilles Debunne33b7de852012-03-12 11:57:48 -07001399 // Will change text color
Gilles Debunned88876a2012-03-16 17:34:04 -07001400 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001401 mEditor.invalidateTextDisplayList();
1402 mEditor.prepareCursorControllers();
Gilles Debunne545c4d42011-11-29 10:37:15 -08001403
Gilles Debunned88876a2012-03-16 17:34:04 -07001404 // start or stop the cursor blinking as appropriate
Gilles Debunne2d373a12012-04-20 15:32:19 -07001405 mEditor.makeBlink();
Gilles Debunned88876a2012-03-16 17:34:04 -07001406 }
Janos Levai042856c2010-10-15 02:53:58 +03001407 }
1408
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001409 /**
1410 * Sets the typeface and style in which the text should be displayed,
1411 * and turns on the fake bold and italic bits in the Paint if the
1412 * Typeface that you provided does not have all the bits in the
1413 * style that you specified.
1414 *
1415 * @attr ref android.R.styleable#TextView_typeface
1416 * @attr ref android.R.styleable#TextView_textStyle
1417 */
1418 public void setTypeface(Typeface tf, int style) {
1419 if (style > 0) {
1420 if (tf == null) {
1421 tf = Typeface.defaultFromStyle(style);
1422 } else {
1423 tf = Typeface.create(tf, style);
1424 }
1425
1426 setTypeface(tf);
1427 // now compute what (if any) algorithmic styling is needed
1428 int typefaceStyle = tf != null ? tf.getStyle() : 0;
1429 int need = style & ~typefaceStyle;
1430 mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
1431 mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
1432 } else {
Victoria Leaseaa0980a2012-06-11 14:46:04 -07001433 mTextPaint.setFakeBoldText(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001434 mTextPaint.setTextSkewX(0);
1435 setTypeface(tf);
1436 }
1437 }
1438
1439 /**
1440 * Subclasses override this to specify that they have a KeyListener
1441 * by default even if not specifically called for in the XML options.
1442 */
1443 protected boolean getDefaultEditable() {
1444 return false;
1445 }
1446
1447 /**
1448 * Subclasses override this to specify a default movement method.
1449 */
1450 protected MovementMethod getDefaultMovementMethod() {
1451 return null;
1452 }
1453
1454 /**
1455 * Return the text the TextView is displaying. If setText() was called with
1456 * an argument of BufferType.SPANNABLE or BufferType.EDITABLE, you can cast
1457 * the return value from this method to Spannable or Editable, respectively.
1458 *
1459 * Note: The content of the return value should not be modified. If you want
1460 * a modifiable one, you should make your own copy first.
Gilles Debunnef03acef2012-04-30 19:26:19 -07001461 *
1462 * @attr ref android.R.styleable#TextView_text
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001463 */
1464 @ViewDebug.CapturedViewProperty
1465 public CharSequence getText() {
1466 return mText;
1467 }
1468
1469 /**
1470 * Returns the length, in characters, of the text managed by this TextView
1471 */
1472 public int length() {
1473 return mText.length();
1474 }
1475
1476 /**
1477 * Return the text the TextView is displaying as an Editable object. If
1478 * the text is not editable, null is returned.
1479 *
1480 * @see #getText
1481 */
1482 public Editable getEditableText() {
1483 return (mText instanceof Editable) ? (Editable)mText : null;
1484 }
1485
1486 /**
1487 * @return the height of one standard line in pixels. Note that markup
1488 * within the text can cause individual lines to be taller or shorter
1489 * than this height, and the layout may contain additional first-
1490 * or last-line padding.
1491 */
1492 public int getLineHeight() {
Gilles Debunne96e6b8b2010-12-14 13:43:45 -08001493 return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001494 }
1495
1496 /**
1497 * @return the Layout that is currently being used to display the text.
1498 * This can be null if the text or width has recently changes.
1499 */
1500 public final Layout getLayout() {
1501 return mLayout;
1502 }
1503
1504 /**
Fabrice Di Meglio0ed59fa2012-05-29 20:32:51 -07001505 * @return the Layout that is currently being used to display the hint text.
1506 * This can be null.
1507 */
1508 final Layout getHintLayout() {
1509 return mHintLayout;
1510 }
1511
1512 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001513 * @return the current key listener for this TextView.
1514 * This will frequently be null for non-EditText TextViews.
Gilles Debunnef03acef2012-04-30 19:26:19 -07001515 *
1516 * @attr ref android.R.styleable#TextView_numeric
1517 * @attr ref android.R.styleable#TextView_digits
1518 * @attr ref android.R.styleable#TextView_phoneNumber
1519 * @attr ref android.R.styleable#TextView_inputMethod
1520 * @attr ref android.R.styleable#TextView_capitalize
1521 * @attr ref android.R.styleable#TextView_autoText
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001522 */
1523 public final KeyListener getKeyListener() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001524 return mEditor == null ? null : mEditor.mKeyListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001525 }
1526
1527 /**
1528 * Sets the key listener to be used with this TextView. This can be null
1529 * to disallow user input. Note that this method has significant and
1530 * subtle interactions with soft keyboards and other input method:
1531 * see {@link KeyListener#getInputType() KeyListener.getContentType()}
1532 * for important details. Calling this method will replace the current
1533 * content type of the text view with the content type returned by the
1534 * key listener.
1535 * <p>
1536 * Be warned that if you want a TextView with a key listener or movement
1537 * method not to be focusable, or if you want a TextView without a
1538 * key listener or movement method to be focusable, you must call
1539 * {@link #setFocusable} again after calling this to get the focusability
1540 * back the way you want it.
1541 *
1542 * @attr ref android.R.styleable#TextView_numeric
1543 * @attr ref android.R.styleable#TextView_digits
1544 * @attr ref android.R.styleable#TextView_phoneNumber
1545 * @attr ref android.R.styleable#TextView_inputMethod
1546 * @attr ref android.R.styleable#TextView_capitalize
1547 * @attr ref android.R.styleable#TextView_autoText
1548 */
1549 public void setKeyListener(KeyListener input) {
1550 setKeyListenerOnly(input);
1551 fixFocusableAndClickableSettings();
1552
1553 if (input != null) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001554 createEditorIfNeeded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001555 try {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001556 mEditor.mInputType = mEditor.mKeyListener.getInputType();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001557 } catch (IncompatibleClassChangeError e) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001558 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001559 }
Gilles Debunned7483bf2010-11-10 10:47:45 -08001560 // Change inputType, without affecting transformation.
1561 // No need to applySingleLine since mSingleLine is unchanged.
1562 setInputTypeSingleLine(mSingleLine);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001563 } else {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001564 if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001565 }
1566
1567 InputMethodManager imm = InputMethodManager.peekInstance();
1568 if (imm != null) imm.restartInput(this);
1569 }
1570
1571 private void setKeyListenerOnly(KeyListener input) {
Gilles Debunne60e21862012-01-30 15:04:14 -08001572 if (mEditor == null && input == null) return; // null is the default value
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001573
Gilles Debunne5fae9962012-05-08 14:53:20 -07001574 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001575 if (mEditor.mKeyListener != input) {
1576 mEditor.mKeyListener = input;
Gilles Debunne60e21862012-01-30 15:04:14 -08001577 if (input != null && !(mText instanceof Editable)) {
1578 setText(mText);
1579 }
1580
1581 setFilters((Editable) mText, mFilters);
1582 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001583 }
1584
1585 /**
1586 * @return the movement method being used for this TextView.
1587 * This will frequently be null for non-EditText TextViews.
1588 */
1589 public final MovementMethod getMovementMethod() {
1590 return mMovement;
1591 }
1592
1593 /**
1594 * Sets the movement method (arrow key handler) to be used for
1595 * this TextView. This can be null to disallow using the arrow keys
1596 * to move the cursor or scroll the view.
1597 * <p>
1598 * Be warned that if you want a TextView with a key listener or movement
1599 * method not to be focusable, or if you want a TextView without a
1600 * key listener or movement method to be focusable, you must call
1601 * {@link #setFocusable} again after calling this to get the focusability
1602 * back the way you want it.
1603 */
1604 public final void setMovementMethod(MovementMethod movement) {
Gilles Debunne60e21862012-01-30 15:04:14 -08001605 if (mMovement != movement) {
1606 mMovement = movement;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001607
Gilles Debunne60e21862012-01-30 15:04:14 -08001608 if (movement != null && !(mText instanceof Spannable)) {
1609 setText(mText);
1610 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001611
Gilles Debunne60e21862012-01-30 15:04:14 -08001612 fixFocusableAndClickableSettings();
Gilles Debunnef788a9f2010-07-22 10:17:23 -07001613
Gilles Debunne2d373a12012-04-20 15:32:19 -07001614 // SelectionModifierCursorController depends on textCanBeSelected, which depends on
1615 // mMovement
1616 if (mEditor != null) mEditor.prepareCursorControllers();
Gilles Debunne60e21862012-01-30 15:04:14 -08001617 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001618 }
1619
1620 private void fixFocusableAndClickableSettings() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001621 if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001622 setFocusable(true);
1623 setClickable(true);
1624 setLongClickable(true);
1625 } else {
1626 setFocusable(false);
1627 setClickable(false);
1628 setLongClickable(false);
1629 }
1630 }
1631
1632 /**
1633 * @return the current transformation method for this TextView.
1634 * This will frequently be null except for single-line and password
1635 * fields.
Gilles Debunnef03acef2012-04-30 19:26:19 -07001636 *
1637 * @attr ref android.R.styleable#TextView_password
1638 * @attr ref android.R.styleable#TextView_singleLine
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001639 */
1640 public final TransformationMethod getTransformationMethod() {
1641 return mTransformation;
1642 }
1643
1644 /**
1645 * Sets the transformation that is applied to the text that this
1646 * TextView is displaying.
1647 *
1648 * @attr ref android.R.styleable#TextView_password
1649 * @attr ref android.R.styleable#TextView_singleLine
1650 */
1651 public final void setTransformationMethod(TransformationMethod method) {
1652 if (method == mTransformation) {
1653 // Avoid the setText() below if the transformation is
1654 // the same.
1655 return;
1656 }
1657 if (mTransformation != null) {
1658 if (mText instanceof Spannable) {
1659 ((Spannable) mText).removeSpan(mTransformation);
1660 }
1661 }
1662
1663 mTransformation = method;
1664
Adam Powell7f8f79a2011-07-07 18:35:54 -07001665 if (method instanceof TransformationMethod2) {
1666 TransformationMethod2 method2 = (TransformationMethod2) method;
Gilles Debunne60e21862012-01-30 15:04:14 -08001667 mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable);
Adam Powell7f8f79a2011-07-07 18:35:54 -07001668 method2.setLengthChangesAllowed(mAllowTransformationLengthChange);
1669 } else {
1670 mAllowTransformationLengthChange = false;
1671 }
1672
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001673 setText(mText);
Svetoslav Ganovc406be92012-05-11 16:12:32 -07001674
1675 if (hasPasswordTransformationMethod()) {
1676 notifyAccessibilityStateChanged();
1677 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001678 }
1679
1680 /**
1681 * Returns the top padding of the view, plus space for the top
1682 * Drawable if any.
1683 */
1684 public int getCompoundPaddingTop() {
1685 final Drawables dr = mDrawables;
1686 if (dr == null || dr.mDrawableTop == null) {
1687 return mPaddingTop;
1688 } else {
1689 return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
1690 }
1691 }
1692
1693 /**
1694 * Returns the bottom padding of the view, plus space for the bottom
1695 * Drawable if any.
1696 */
1697 public int getCompoundPaddingBottom() {
1698 final Drawables dr = mDrawables;
1699 if (dr == null || dr.mDrawableBottom == null) {
1700 return mPaddingBottom;
1701 } else {
1702 return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
1703 }
1704 }
1705
1706 /**
1707 * Returns the left padding of the view, plus space for the left
1708 * Drawable if any.
1709 */
1710 public int getCompoundPaddingLeft() {
1711 final Drawables dr = mDrawables;
1712 if (dr == null || dr.mDrawableLeft == null) {
1713 return mPaddingLeft;
1714 } else {
1715 return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
1716 }
1717 }
1718
1719 /**
1720 * Returns the right padding of the view, plus space for the right
1721 * Drawable if any.
1722 */
1723 public int getCompoundPaddingRight() {
1724 final Drawables dr = mDrawables;
1725 if (dr == null || dr.mDrawableRight == null) {
1726 return mPaddingRight;
1727 } else {
1728 return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
1729 }
1730 }
1731
1732 /**
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001733 * Returns the start padding of the view, plus space for the start
1734 * Drawable if any.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001735 */
1736 public int getCompoundPaddingStart() {
1737 resolveDrawables();
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07001738 switch(getLayoutDirection()) {
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001739 default:
1740 case LAYOUT_DIRECTION_LTR:
1741 return getCompoundPaddingLeft();
1742 case LAYOUT_DIRECTION_RTL:
1743 return getCompoundPaddingRight();
1744 }
1745 }
1746
1747 /**
1748 * Returns the end padding of the view, plus space for the end
1749 * Drawable if any.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001750 */
1751 public int getCompoundPaddingEnd() {
1752 resolveDrawables();
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07001753 switch(getLayoutDirection()) {
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001754 default:
1755 case LAYOUT_DIRECTION_LTR:
1756 return getCompoundPaddingRight();
1757 case LAYOUT_DIRECTION_RTL:
1758 return getCompoundPaddingLeft();
1759 }
1760 }
1761
1762 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001763 * Returns the extended top padding of the view, including both the
1764 * top Drawable if any and any extra space to keep more than maxLines
1765 * of text from showing. It is only valid to call this after measuring.
1766 */
1767 public int getExtendedPaddingTop() {
1768 if (mMaxMode != LINES) {
1769 return getCompoundPaddingTop();
1770 }
1771
1772 if (mLayout.getLineCount() <= mMaximum) {
1773 return getCompoundPaddingTop();
1774 }
1775
1776 int top = getCompoundPaddingTop();
1777 int bottom = getCompoundPaddingBottom();
1778 int viewht = getHeight() - top - bottom;
1779 int layoutht = mLayout.getLineTop(mMaximum);
1780
1781 if (layoutht >= viewht) {
1782 return top;
1783 }
1784
1785 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
1786 if (gravity == Gravity.TOP) {
1787 return top;
1788 } else if (gravity == Gravity.BOTTOM) {
1789 return top + viewht - layoutht;
1790 } else { // (gravity == Gravity.CENTER_VERTICAL)
1791 return top + (viewht - layoutht) / 2;
1792 }
1793 }
1794
1795 /**
1796 * Returns the extended bottom padding of the view, including both the
1797 * bottom Drawable if any and any extra space to keep more than maxLines
1798 * of text from showing. It is only valid to call this after measuring.
1799 */
1800 public int getExtendedPaddingBottom() {
1801 if (mMaxMode != LINES) {
1802 return getCompoundPaddingBottom();
1803 }
1804
1805 if (mLayout.getLineCount() <= mMaximum) {
1806 return getCompoundPaddingBottom();
1807 }
1808
1809 int top = getCompoundPaddingTop();
1810 int bottom = getCompoundPaddingBottom();
1811 int viewht = getHeight() - top - bottom;
1812 int layoutht = mLayout.getLineTop(mMaximum);
1813
1814 if (layoutht >= viewht) {
1815 return bottom;
1816 }
1817
1818 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
1819 if (gravity == Gravity.TOP) {
1820 return bottom + viewht - layoutht;
1821 } else if (gravity == Gravity.BOTTOM) {
1822 return bottom;
1823 } else { // (gravity == Gravity.CENTER_VERTICAL)
1824 return bottom + (viewht - layoutht) / 2;
1825 }
1826 }
1827
1828 /**
1829 * Returns the total left padding of the view, including the left
1830 * Drawable if any.
1831 */
1832 public int getTotalPaddingLeft() {
1833 return getCompoundPaddingLeft();
1834 }
1835
1836 /**
1837 * Returns the total right padding of the view, including the right
1838 * Drawable if any.
1839 */
1840 public int getTotalPaddingRight() {
1841 return getCompoundPaddingRight();
1842 }
1843
1844 /**
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001845 * Returns the total start padding of the view, including the start
1846 * Drawable if any.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001847 */
1848 public int getTotalPaddingStart() {
1849 return getCompoundPaddingStart();
1850 }
1851
1852 /**
1853 * Returns the total end padding of the view, including the end
1854 * Drawable if any.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001855 */
1856 public int getTotalPaddingEnd() {
1857 return getCompoundPaddingEnd();
1858 }
1859
1860 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001861 * Returns the total top padding of the view, including the top
1862 * Drawable if any, the extra space to keep more than maxLines
1863 * from showing, and the vertical offset for gravity, if any.
1864 */
1865 public int getTotalPaddingTop() {
1866 return getExtendedPaddingTop() + getVerticalOffset(true);
1867 }
1868
1869 /**
1870 * Returns the total bottom padding of the view, including the bottom
1871 * Drawable if any, the extra space to keep more than maxLines
1872 * from showing, and the vertical offset for gravity, if any.
1873 */
1874 public int getTotalPaddingBottom() {
1875 return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
1876 }
1877
1878 /**
1879 * Sets the Drawables (if any) to appear to the left of, above,
1880 * to the right of, and below the text. Use null if you do not
1881 * want a Drawable there. The Drawables must already have had
1882 * {@link Drawable#setBounds} called.
1883 *
1884 * @attr ref android.R.styleable#TextView_drawableLeft
1885 * @attr ref android.R.styleable#TextView_drawableTop
1886 * @attr ref android.R.styleable#TextView_drawableRight
1887 * @attr ref android.R.styleable#TextView_drawableBottom
1888 */
1889 public void setCompoundDrawables(Drawable left, Drawable top,
1890 Drawable right, Drawable bottom) {
1891 Drawables dr = mDrawables;
1892
1893 final boolean drawables = left != null || top != null
1894 || right != null || bottom != null;
1895
1896 if (!drawables) {
1897 // Clearing drawables... can we free the data structure?
1898 if (dr != null) {
1899 if (dr.mDrawablePadding == 0) {
1900 mDrawables = null;
1901 } else {
1902 // We need to retain the last set padding, so just clear
1903 // out all of the fields in the existing structure.
Romain Guy48540eb2009-05-19 16:44:57 -07001904 if (dr.mDrawableLeft != null) dr.mDrawableLeft.setCallback(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001905 dr.mDrawableLeft = null;
Romain Guy48540eb2009-05-19 16:44:57 -07001906 if (dr.mDrawableTop != null) dr.mDrawableTop.setCallback(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001907 dr.mDrawableTop = null;
Romain Guy48540eb2009-05-19 16:44:57 -07001908 if (dr.mDrawableRight != null) dr.mDrawableRight.setCallback(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001909 dr.mDrawableRight = null;
Romain Guy48540eb2009-05-19 16:44:57 -07001910 if (dr.mDrawableBottom != null) dr.mDrawableBottom.setCallback(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001911 dr.mDrawableBottom = null;
1912 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
1913 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
1914 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
1915 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
1916 }
1917 }
1918 } else {
1919 if (dr == null) {
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07001920 mDrawables = dr = new Drawables(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001921 }
1922
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07001923 mDrawables.mOverride = false;
1924
Romain Guy48540eb2009-05-19 16:44:57 -07001925 if (dr.mDrawableLeft != left && dr.mDrawableLeft != null) {
1926 dr.mDrawableLeft.setCallback(null);
1927 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001928 dr.mDrawableLeft = left;
Romain Guy8e618e52010-03-08 12:18:20 -08001929
1930 if (dr.mDrawableTop != top && dr.mDrawableTop != null) {
Romain Guy48540eb2009-05-19 16:44:57 -07001931 dr.mDrawableTop.setCallback(null);
1932 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001933 dr.mDrawableTop = top;
Romain Guy8e618e52010-03-08 12:18:20 -08001934
1935 if (dr.mDrawableRight != right && dr.mDrawableRight != null) {
Romain Guy48540eb2009-05-19 16:44:57 -07001936 dr.mDrawableRight.setCallback(null);
1937 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001938 dr.mDrawableRight = right;
Romain Guy8e618e52010-03-08 12:18:20 -08001939
1940 if (dr.mDrawableBottom != bottom && dr.mDrawableBottom != null) {
Romain Guy48540eb2009-05-19 16:44:57 -07001941 dr.mDrawableBottom.setCallback(null);
1942 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001943 dr.mDrawableBottom = bottom;
1944
1945 final Rect compoundRect = dr.mCompoundRect;
Romain Guy48540eb2009-05-19 16:44:57 -07001946 int[] state;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001947
1948 state = getDrawableState();
1949
1950 if (left != null) {
1951 left.setState(state);
1952 left.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07001953 left.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001954 dr.mDrawableSizeLeft = compoundRect.width();
1955 dr.mDrawableHeightLeft = compoundRect.height();
1956 } else {
1957 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
1958 }
1959
1960 if (right != null) {
1961 right.setState(state);
1962 right.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07001963 right.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001964 dr.mDrawableSizeRight = compoundRect.width();
1965 dr.mDrawableHeightRight = compoundRect.height();
1966 } else {
1967 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
1968 }
1969
1970 if (top != null) {
1971 top.setState(state);
1972 top.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07001973 top.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001974 dr.mDrawableSizeTop = compoundRect.height();
1975 dr.mDrawableWidthTop = compoundRect.width();
1976 } else {
1977 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
1978 }
1979
1980 if (bottom != null) {
1981 bottom.setState(state);
1982 bottom.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07001983 bottom.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001984 dr.mDrawableSizeBottom = compoundRect.height();
1985 dr.mDrawableWidthBottom = compoundRect.width();
1986 } else {
1987 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
1988 }
1989 }
1990
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07001991 // Save initial left/right drawables
1992 if (dr != null) {
1993 dr.mDrawableLeftInitial = left;
1994 dr.mDrawableRightInitial = right;
1995 }
1996
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001997 invalidate();
1998 requestLayout();
1999 }
2000
2001 /**
2002 * Sets the Drawables (if any) to appear to the left of, above,
2003 * to the right of, and below the text. Use 0 if you do not
2004 * want a Drawable there. The Drawables' bounds will be set to
2005 * their intrinsic bounds.
2006 *
2007 * @param left Resource identifier of the left Drawable.
2008 * @param top Resource identifier of the top Drawable.
2009 * @param right Resource identifier of the right Drawable.
2010 * @param bottom Resource identifier of the bottom Drawable.
2011 *
2012 * @attr ref android.R.styleable#TextView_drawableLeft
2013 * @attr ref android.R.styleable#TextView_drawableTop
2014 * @attr ref android.R.styleable#TextView_drawableRight
2015 * @attr ref android.R.styleable#TextView_drawableBottom
2016 */
Daniel Sandler820ba322012-03-23 16:36:00 -05002017 @android.view.RemotableViewMethod
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002018 public void setCompoundDrawablesWithIntrinsicBounds(int left, int top, int right, int bottom) {
2019 final Resources resources = getContext().getResources();
2020 setCompoundDrawablesWithIntrinsicBounds(left != 0 ? resources.getDrawable(left) : null,
2021 top != 0 ? resources.getDrawable(top) : null,
2022 right != 0 ? resources.getDrawable(right) : null,
2023 bottom != 0 ? resources.getDrawable(bottom) : null);
2024 }
2025
2026 /**
2027 * Sets the Drawables (if any) to appear to the left of, above,
2028 * to the right of, and below the text. Use null if you do not
2029 * want a Drawable there. The Drawables' bounds will be set to
2030 * their intrinsic bounds.
2031 *
2032 * @attr ref android.R.styleable#TextView_drawableLeft
2033 * @attr ref android.R.styleable#TextView_drawableTop
2034 * @attr ref android.R.styleable#TextView_drawableRight
2035 * @attr ref android.R.styleable#TextView_drawableBottom
2036 */
2037 public void setCompoundDrawablesWithIntrinsicBounds(Drawable left, Drawable top,
2038 Drawable right, Drawable bottom) {
2039
2040 if (left != null) {
2041 left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
2042 }
2043 if (right != null) {
2044 right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
2045 }
2046 if (top != null) {
2047 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
2048 }
2049 if (bottom != null) {
2050 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
2051 }
2052 setCompoundDrawables(left, top, right, bottom);
2053 }
2054
2055 /**
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002056 * Sets the Drawables (if any) to appear to the start of, above,
2057 * to the end of, and below the text. Use null if you do not
2058 * want a Drawable there. The Drawables must already have had
2059 * {@link Drawable#setBounds} called.
2060 *
2061 * @attr ref android.R.styleable#TextView_drawableStart
2062 * @attr ref android.R.styleable#TextView_drawableTop
2063 * @attr ref android.R.styleable#TextView_drawableEnd
2064 * @attr ref android.R.styleable#TextView_drawableBottom
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002065 */
2066 public void setCompoundDrawablesRelative(Drawable start, Drawable top,
2067 Drawable end, Drawable bottom) {
2068 Drawables dr = mDrawables;
2069
2070 final boolean drawables = start != null || top != null
2071 || end != null || bottom != null;
2072
2073 if (!drawables) {
2074 // Clearing drawables... can we free the data structure?
2075 if (dr != null) {
2076 if (dr.mDrawablePadding == 0) {
2077 mDrawables = null;
2078 } else {
2079 // We need to retain the last set padding, so just clear
2080 // out all of the fields in the existing structure.
2081 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
2082 dr.mDrawableStart = null;
2083 if (dr.mDrawableTop != null) dr.mDrawableTop.setCallback(null);
2084 dr.mDrawableTop = null;
2085 if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null);
2086 dr.mDrawableEnd = null;
2087 if (dr.mDrawableBottom != null) dr.mDrawableBottom.setCallback(null);
2088 dr.mDrawableBottom = null;
2089 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2090 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2091 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2092 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2093 }
2094 }
2095 } else {
2096 if (dr == null) {
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002097 mDrawables = dr = new Drawables(getContext());
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002098 }
2099
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002100 mDrawables.mOverride = true;
2101
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002102 if (dr.mDrawableStart != start && dr.mDrawableStart != null) {
2103 dr.mDrawableStart.setCallback(null);
2104 }
2105 dr.mDrawableStart = start;
2106
2107 if (dr.mDrawableTop != top && dr.mDrawableTop != null) {
2108 dr.mDrawableTop.setCallback(null);
2109 }
2110 dr.mDrawableTop = top;
2111
2112 if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) {
2113 dr.mDrawableEnd.setCallback(null);
2114 }
2115 dr.mDrawableEnd = end;
2116
2117 if (dr.mDrawableBottom != bottom && dr.mDrawableBottom != null) {
2118 dr.mDrawableBottom.setCallback(null);
2119 }
2120 dr.mDrawableBottom = bottom;
2121
2122 final Rect compoundRect = dr.mCompoundRect;
2123 int[] state;
2124
2125 state = getDrawableState();
2126
2127 if (start != null) {
2128 start.setState(state);
2129 start.copyBounds(compoundRect);
2130 start.setCallback(this);
2131 dr.mDrawableSizeStart = compoundRect.width();
2132 dr.mDrawableHeightStart = compoundRect.height();
2133 } else {
2134 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2135 }
2136
2137 if (end != null) {
2138 end.setState(state);
2139 end.copyBounds(compoundRect);
2140 end.setCallback(this);
2141 dr.mDrawableSizeEnd = compoundRect.width();
2142 dr.mDrawableHeightEnd = compoundRect.height();
2143 } else {
2144 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2145 }
2146
2147 if (top != null) {
2148 top.setState(state);
2149 top.copyBounds(compoundRect);
2150 top.setCallback(this);
2151 dr.mDrawableSizeTop = compoundRect.height();
2152 dr.mDrawableWidthTop = compoundRect.width();
2153 } else {
2154 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2155 }
2156
2157 if (bottom != null) {
2158 bottom.setState(state);
2159 bottom.copyBounds(compoundRect);
2160 bottom.setCallback(this);
2161 dr.mDrawableSizeBottom = compoundRect.height();
2162 dr.mDrawableWidthBottom = compoundRect.width();
2163 } else {
2164 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2165 }
2166 }
2167
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002168 resetResolvedDrawables();
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002169 resolveDrawables();
2170 invalidate();
2171 requestLayout();
2172 }
2173
2174 /**
2175 * Sets the Drawables (if any) to appear to the start of, above,
2176 * to the end of, and below the text. Use 0 if you do not
2177 * want a Drawable there. The Drawables' bounds will be set to
2178 * their intrinsic bounds.
2179 *
2180 * @param start Resource identifier of the start Drawable.
2181 * @param top Resource identifier of the top Drawable.
2182 * @param end Resource identifier of the end Drawable.
2183 * @param bottom Resource identifier of the bottom Drawable.
2184 *
2185 * @attr ref android.R.styleable#TextView_drawableStart
2186 * @attr ref android.R.styleable#TextView_drawableTop
2187 * @attr ref android.R.styleable#TextView_drawableEnd
2188 * @attr ref android.R.styleable#TextView_drawableBottom
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002189 */
Daniel Sandler820ba322012-03-23 16:36:00 -05002190 @android.view.RemotableViewMethod
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002191 public void setCompoundDrawablesRelativeWithIntrinsicBounds(int start, int top, int end,
2192 int bottom) {
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002193 final Resources resources = getContext().getResources();
2194 setCompoundDrawablesRelativeWithIntrinsicBounds(
2195 start != 0 ? resources.getDrawable(start) : null,
2196 top != 0 ? resources.getDrawable(top) : null,
2197 end != 0 ? resources.getDrawable(end) : null,
2198 bottom != 0 ? resources.getDrawable(bottom) : null);
2199 }
2200
2201 /**
2202 * Sets the Drawables (if any) to appear to the start of, above,
2203 * to the end of, and below the text. Use null if you do not
2204 * want a Drawable there. The Drawables' bounds will be set to
2205 * their intrinsic bounds.
2206 *
2207 * @attr ref android.R.styleable#TextView_drawableStart
2208 * @attr ref android.R.styleable#TextView_drawableTop
2209 * @attr ref android.R.styleable#TextView_drawableEnd
2210 * @attr ref android.R.styleable#TextView_drawableBottom
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002211 */
2212 public void setCompoundDrawablesRelativeWithIntrinsicBounds(Drawable start, Drawable top,
2213 Drawable end, Drawable bottom) {
2214
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002215 if (start != null) {
2216 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
2217 }
2218 if (end != null) {
2219 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
2220 }
2221 if (top != null) {
2222 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
2223 }
2224 if (bottom != null) {
2225 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
2226 }
2227 setCompoundDrawablesRelative(start, top, end, bottom);
2228 }
2229
2230 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002231 * Returns drawables for the left, top, right, and bottom borders.
Gilles Debunnef03acef2012-04-30 19:26:19 -07002232 *
2233 * @attr ref android.R.styleable#TextView_drawableLeft
2234 * @attr ref android.R.styleable#TextView_drawableTop
2235 * @attr ref android.R.styleable#TextView_drawableRight
2236 * @attr ref android.R.styleable#TextView_drawableBottom
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002237 */
2238 public Drawable[] getCompoundDrawables() {
2239 final Drawables dr = mDrawables;
2240 if (dr != null) {
2241 return new Drawable[] {
2242 dr.mDrawableLeft, dr.mDrawableTop, dr.mDrawableRight, dr.mDrawableBottom
2243 };
2244 } else {
2245 return new Drawable[] { null, null, null, null };
2246 }
2247 }
2248
2249 /**
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002250 * Returns drawables for the start, top, end, and bottom borders.
Gilles Debunnef03acef2012-04-30 19:26:19 -07002251 *
2252 * @attr ref android.R.styleable#TextView_drawableStart
2253 * @attr ref android.R.styleable#TextView_drawableTop
2254 * @attr ref android.R.styleable#TextView_drawableEnd
2255 * @attr ref android.R.styleable#TextView_drawableBottom
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002256 */
2257 public Drawable[] getCompoundDrawablesRelative() {
2258 final Drawables dr = mDrawables;
2259 if (dr != null) {
2260 return new Drawable[] {
2261 dr.mDrawableStart, dr.mDrawableTop, dr.mDrawableEnd, dr.mDrawableBottom
2262 };
2263 } else {
2264 return new Drawable[] { null, null, null, null };
2265 }
2266 }
2267
2268 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002269 * Sets the size of the padding between the compound drawables and
2270 * the text.
2271 *
2272 * @attr ref android.R.styleable#TextView_drawablePadding
2273 */
Daniel Sandler820ba322012-03-23 16:36:00 -05002274 @android.view.RemotableViewMethod
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002275 public void setCompoundDrawablePadding(int pad) {
2276 Drawables dr = mDrawables;
2277 if (pad == 0) {
2278 if (dr != null) {
2279 dr.mDrawablePadding = pad;
2280 }
2281 } else {
2282 if (dr == null) {
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002283 mDrawables = dr = new Drawables(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002284 }
2285 dr.mDrawablePadding = pad;
2286 }
2287
2288 invalidate();
2289 requestLayout();
2290 }
2291
2292 /**
2293 * Returns the padding between the compound drawables and the text.
Gilles Debunnef03acef2012-04-30 19:26:19 -07002294 *
2295 * @attr ref android.R.styleable#TextView_drawablePadding
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002296 */
2297 public int getCompoundDrawablePadding() {
2298 final Drawables dr = mDrawables;
2299 return dr != null ? dr.mDrawablePadding : 0;
2300 }
2301
2302 @Override
2303 public void setPadding(int left, int top, int right, int bottom) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07002304 if (left != mPaddingLeft ||
2305 right != mPaddingRight ||
2306 top != mPaddingTop ||
2307 bottom != mPaddingBottom) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002308 nullLayouts();
2309 }
2310
2311 // the super call will requestLayout()
2312 super.setPadding(left, top, right, bottom);
2313 invalidate();
2314 }
2315
Fabrice Di Megliobf923eb2012-03-07 16:20:22 -08002316 @Override
2317 public void setPaddingRelative(int start, int top, int end, int bottom) {
2318 if (start != getPaddingStart() ||
2319 end != getPaddingEnd() ||
2320 top != mPaddingTop ||
2321 bottom != mPaddingBottom) {
2322 nullLayouts();
2323 }
2324
2325 // the super call will requestLayout()
2326 super.setPaddingRelative(start, top, end, bottom);
2327 invalidate();
2328 }
2329
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002330 /**
2331 * Gets the autolink mask of the text. See {@link
2332 * android.text.util.Linkify#ALL Linkify.ALL} and peers for
2333 * possible values.
2334 *
2335 * @attr ref android.R.styleable#TextView_autoLink
2336 */
2337 public final int getAutoLinkMask() {
2338 return mAutoLinkMask;
2339 }
2340
2341 /**
2342 * Sets the text color, size, style, hint color, and highlight color
2343 * from the specified TextAppearance resource.
2344 */
2345 public void setTextAppearance(Context context, int resid) {
2346 TypedArray appearance =
2347 context.obtainStyledAttributes(resid,
2348 com.android.internal.R.styleable.TextAppearance);
2349
2350 int color;
2351 ColorStateList colors;
2352 int ts;
2353
Gilles Debunne2d373a12012-04-20 15:32:19 -07002354 color = appearance.getColor(
2355 com.android.internal.R.styleable.TextAppearance_textColorHighlight, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002356 if (color != 0) {
2357 setHighlightColor(color);
2358 }
2359
2360 colors = appearance.getColorStateList(com.android.internal.R.styleable.
2361 TextAppearance_textColor);
2362 if (colors != null) {
2363 setTextColor(colors);
2364 }
2365
2366 ts = appearance.getDimensionPixelSize(com.android.internal.R.styleable.
2367 TextAppearance_textSize, 0);
2368 if (ts != 0) {
2369 setRawTextSize(ts);
2370 }
2371
2372 colors = appearance.getColorStateList(com.android.internal.R.styleable.
2373 TextAppearance_textColorHint);
2374 if (colors != null) {
2375 setHintTextColor(colors);
2376 }
2377
2378 colors = appearance.getColorStateList(com.android.internal.R.styleable.
2379 TextAppearance_textColorLink);
2380 if (colors != null) {
2381 setLinkTextColor(colors);
2382 }
2383
Raph Leviend570e892012-05-09 11:45:34 -07002384 String familyName;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002385 int typefaceIndex, styleIndex;
2386
Raph Leviend570e892012-05-09 11:45:34 -07002387 familyName = appearance.getString(com.android.internal.R.styleable.
2388 TextAppearance_fontFamily);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002389 typefaceIndex = appearance.getInt(com.android.internal.R.styleable.
2390 TextAppearance_typeface, -1);
2391 styleIndex = appearance.getInt(com.android.internal.R.styleable.
2392 TextAppearance_textStyle, -1);
2393
Raph Leviend570e892012-05-09 11:45:34 -07002394 setTypefaceFromAttrs(familyName, typefaceIndex, styleIndex);
Gilles Debunne21078e42011-08-02 10:22:35 -07002395
Adam Powellac91df82013-02-14 13:48:47 -08002396 final int shadowcolor = appearance.getInt(
2397 com.android.internal.R.styleable.TextAppearance_shadowColor, 0);
2398 if (shadowcolor != 0) {
2399 final float dx = appearance.getFloat(
2400 com.android.internal.R.styleable.TextAppearance_shadowDx, 0);
2401 final float dy = appearance.getFloat(
2402 com.android.internal.R.styleable.TextAppearance_shadowDy, 0);
2403 final float r = appearance.getFloat(
2404 com.android.internal.R.styleable.TextAppearance_shadowRadius, 0);
2405
2406 setShadowLayer(r, dx, dy, shadowcolor);
2407 }
2408
Adam Powell7f8f79a2011-07-07 18:35:54 -07002409 if (appearance.getBoolean(com.android.internal.R.styleable.TextAppearance_textAllCaps,
2410 false)) {
2411 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
2412 }
2413
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002414 appearance.recycle();
2415 }
2416
2417 /**
Victoria Leasedf8ef4b2012-08-17 15:34:01 -07002418 * Get the default {@link Locale} of the text in this TextView.
2419 * @return the default {@link Locale} of the text in this TextView.
2420 */
2421 public Locale getTextLocale() {
2422 return mTextPaint.getTextLocale();
2423 }
2424
2425 /**
2426 * Set the default {@link Locale} of the text in this TextView to the given value. This value
2427 * is used to choose appropriate typefaces for ambiguous characters. Typically used for CJK
2428 * locales to disambiguate Hanzi/Kanji/Hanja characters.
2429 *
2430 * @param locale the {@link Locale} for drawing text, must not be null.
2431 *
2432 * @see Paint#setTextLocale
2433 */
2434 public void setTextLocale(Locale locale) {
2435 mTextPaint.setTextLocale(locale);
2436 }
2437
2438 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002439 * @return the size (in pixels) of the default text size in this TextView.
2440 */
Fabrice Di Meglioc54da1c2012-04-27 16:16:35 -07002441 @ViewDebug.ExportedProperty(category = "text")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002442 public float getTextSize() {
2443 return mTextPaint.getTextSize();
2444 }
2445
2446 /**
2447 * Set the default text size to the given value, interpreted as "scaled
2448 * pixel" units. This size is adjusted based on the current density and
2449 * user font size preference.
2450 *
2451 * @param size The scaled pixel size.
2452 *
2453 * @attr ref android.R.styleable#TextView_textSize
2454 */
2455 @android.view.RemotableViewMethod
2456 public void setTextSize(float size) {
2457 setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
2458 }
2459
2460 /**
2461 * Set the default text size to a given unit and value. See {@link
2462 * TypedValue} for the possible dimension units.
2463 *
2464 * @param unit The desired dimension unit.
2465 * @param size The desired size in the given units.
2466 *
2467 * @attr ref android.R.styleable#TextView_textSize
2468 */
2469 public void setTextSize(int unit, float size) {
2470 Context c = getContext();
2471 Resources r;
2472
2473 if (c == null)
2474 r = Resources.getSystem();
2475 else
2476 r = c.getResources();
2477
2478 setRawTextSize(TypedValue.applyDimension(
2479 unit, size, r.getDisplayMetrics()));
2480 }
2481
2482 private void setRawTextSize(float size) {
2483 if (size != mTextPaint.getTextSize()) {
2484 mTextPaint.setTextSize(size);
2485
2486 if (mLayout != null) {
2487 nullLayouts();
2488 requestLayout();
2489 invalidate();
2490 }
2491 }
2492 }
2493
2494 /**
2495 * @return the extent by which text is currently being stretched
2496 * horizontally. This will usually be 1.
2497 */
2498 public float getTextScaleX() {
2499 return mTextPaint.getTextScaleX();
2500 }
2501
2502 /**
2503 * Sets the extent by which text should be stretched horizontally.
2504 *
2505 * @attr ref android.R.styleable#TextView_textScaleX
2506 */
2507 @android.view.RemotableViewMethod
2508 public void setTextScaleX(float size) {
2509 if (size != mTextPaint.getTextScaleX()) {
Romain Guy939151f2009-04-08 14:22:40 -07002510 mUserSetTextScaleX = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002511 mTextPaint.setTextScaleX(size);
2512
2513 if (mLayout != null) {
2514 nullLayouts();
2515 requestLayout();
2516 invalidate();
2517 }
2518 }
2519 }
2520
2521 /**
2522 * Sets the typeface and style in which the text should be displayed.
2523 * Note that not all Typeface families actually have bold and italic
2524 * variants, so you may need to use
2525 * {@link #setTypeface(Typeface, int)} to get the appearance
2526 * that you actually want.
2527 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002528 * @see #getTypeface()
2529 *
Raph Leviend570e892012-05-09 11:45:34 -07002530 * @attr ref android.R.styleable#TextView_fontFamily
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002531 * @attr ref android.R.styleable#TextView_typeface
2532 * @attr ref android.R.styleable#TextView_textStyle
2533 */
2534 public void setTypeface(Typeface tf) {
2535 if (mTextPaint.getTypeface() != tf) {
2536 mTextPaint.setTypeface(tf);
2537
2538 if (mLayout != null) {
2539 nullLayouts();
2540 requestLayout();
2541 invalidate();
2542 }
2543 }
2544 }
2545
2546 /**
2547 * @return the current typeface and style in which the text is being
2548 * displayed.
Gilles Debunnef03acef2012-04-30 19:26:19 -07002549 *
2550 * @see #setTypeface(Typeface)
2551 *
Raph Leviend570e892012-05-09 11:45:34 -07002552 * @attr ref android.R.styleable#TextView_fontFamily
Gilles Debunnef03acef2012-04-30 19:26:19 -07002553 * @attr ref android.R.styleable#TextView_typeface
2554 * @attr ref android.R.styleable#TextView_textStyle
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002555 */
2556 public Typeface getTypeface() {
2557 return mTextPaint.getTypeface();
2558 }
2559
2560 /**
2561 * Sets the text color for all the states (normal, selected,
2562 * focused) to be this color.
2563 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002564 * @see #setTextColor(ColorStateList)
2565 * @see #getTextColors()
2566 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002567 * @attr ref android.R.styleable#TextView_textColor
2568 */
2569 @android.view.RemotableViewMethod
2570 public void setTextColor(int color) {
2571 mTextColor = ColorStateList.valueOf(color);
2572 updateTextColors();
2573 }
2574
2575 /**
2576 * Sets the text color.
2577 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002578 * @see #setTextColor(int)
2579 * @see #getTextColors()
2580 * @see #setHintTextColor(ColorStateList)
2581 * @see #setLinkTextColor(ColorStateList)
2582 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002583 * @attr ref android.R.styleable#TextView_textColor
2584 */
2585 public void setTextColor(ColorStateList colors) {
2586 if (colors == null) {
2587 throw new NullPointerException();
2588 }
2589
2590 mTextColor = colors;
2591 updateTextColors();
2592 }
2593
2594 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002595 * Gets the text colors for the different states (normal, selected, focused) of the TextView.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002596 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002597 * @see #setTextColor(ColorStateList)
2598 * @see #setTextColor(int)
2599 *
2600 * @attr ref android.R.styleable#TextView_textColor
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002601 */
2602 public final ColorStateList getTextColors() {
2603 return mTextColor;
2604 }
2605
2606 /**
2607 * <p>Return the current color selected for normal text.</p>
2608 *
2609 * @return Returns the current text color.
2610 */
2611 public final int getCurrentTextColor() {
2612 return mCurTextColor;
2613 }
2614
2615 /**
2616 * Sets the color used to display the selection highlight.
2617 *
2618 * @attr ref android.R.styleable#TextView_textColorHighlight
2619 */
2620 @android.view.RemotableViewMethod
2621 public void setHighlightColor(int color) {
2622 if (mHighlightColor != color) {
2623 mHighlightColor = color;
2624 invalidate();
2625 }
2626 }
2627
2628 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002629 * @return the color used to display the selection highlight
2630 *
2631 * @see #setHighlightColor(int)
2632 *
2633 * @attr ref android.R.styleable#TextView_textColorHighlight
2634 */
2635 public int getHighlightColor() {
2636 return mHighlightColor;
2637 }
2638
2639 /**
Gilles Debunne3473b2b2012-04-20 16:21:10 -07002640 * Sets whether the soft input method will be made visible when this
2641 * TextView gets focused. The default is true.
2642 * @hide
2643 */
2644 @android.view.RemotableViewMethod
2645 public final void setShowSoftInputOnFocus(boolean show) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07002646 createEditorIfNeeded();
Gilles Debunne3473b2b2012-04-20 16:21:10 -07002647 mEditor.mShowSoftInputOnFocus = show;
2648 }
2649
2650 /**
2651 * Returns whether the soft input method will be made visible when this
2652 * TextView gets focused. The default is true.
2653 * @hide
2654 */
2655 public final boolean getShowSoftInputOnFocus() {
2656 // When there is no Editor, return default true value
2657 return mEditor == null || mEditor.mShowSoftInputOnFocus;
2658 }
2659
2660 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002661 * Gives the text a shadow of the specified radius and color, the specified
2662 * distance from its normal position.
2663 *
2664 * @attr ref android.R.styleable#TextView_shadowColor
2665 * @attr ref android.R.styleable#TextView_shadowDx
2666 * @attr ref android.R.styleable#TextView_shadowDy
2667 * @attr ref android.R.styleable#TextView_shadowRadius
2668 */
2669 public void setShadowLayer(float radius, float dx, float dy, int color) {
2670 mTextPaint.setShadowLayer(radius, dx, dy, color);
2671
2672 mShadowRadius = radius;
2673 mShadowDx = dx;
2674 mShadowDy = dy;
2675
Gilles Debunne33b7de852012-03-12 11:57:48 -07002676 // Will change text clip region
Gilles Debunne2d373a12012-04-20 15:32:19 -07002677 if (mEditor != null) mEditor.invalidateTextDisplayList();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002678 invalidate();
2679 }
2680
2681 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002682 * Gets the radius of the shadow layer.
2683 *
2684 * @return the radius of the shadow layer. If 0, the shadow layer is not visible
2685 *
2686 * @see #setShadowLayer(float, float, float, int)
2687 *
2688 * @attr ref android.R.styleable#TextView_shadowRadius
2689 */
2690 public float getShadowRadius() {
2691 return mShadowRadius;
2692 }
2693
2694 /**
2695 * @return the horizontal offset of the shadow layer
2696 *
2697 * @see #setShadowLayer(float, float, float, int)
2698 *
2699 * @attr ref android.R.styleable#TextView_shadowDx
2700 */
2701 public float getShadowDx() {
2702 return mShadowDx;
2703 }
2704
2705 /**
2706 * @return the vertical offset of the shadow layer
2707 *
2708 * @see #setShadowLayer(float, float, float, int)
2709 *
2710 * @attr ref android.R.styleable#TextView_shadowDy
2711 */
2712 public float getShadowDy() {
2713 return mShadowDy;
2714 }
2715
2716 /**
2717 * @return the color of the shadow layer
2718 *
2719 * @see #setShadowLayer(float, float, float, int)
2720 *
2721 * @attr ref android.R.styleable#TextView_shadowColor
2722 */
2723 public int getShadowColor() {
2724 return mTextPaint.shadowColor;
2725 }
2726
2727 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002728 * @return the base paint used for the text. Please use this only to
2729 * consult the Paint's properties and not to change them.
2730 */
2731 public TextPaint getPaint() {
2732 return mTextPaint;
2733 }
2734
2735 /**
2736 * Sets the autolink mask of the text. See {@link
2737 * android.text.util.Linkify#ALL Linkify.ALL} and peers for
2738 * possible values.
2739 *
2740 * @attr ref android.R.styleable#TextView_autoLink
2741 */
2742 @android.view.RemotableViewMethod
2743 public final void setAutoLinkMask(int mask) {
2744 mAutoLinkMask = mask;
2745 }
2746
2747 /**
2748 * Sets whether the movement method will automatically be set to
2749 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
2750 * set to nonzero and links are detected in {@link #setText}.
2751 * The default is true.
2752 *
2753 * @attr ref android.R.styleable#TextView_linksClickable
2754 */
2755 @android.view.RemotableViewMethod
2756 public final void setLinksClickable(boolean whether) {
2757 mLinksClickable = whether;
2758 }
2759
2760 /**
2761 * Returns whether the movement method will automatically be set to
2762 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
2763 * set to nonzero and links are detected in {@link #setText}.
2764 * The default is true.
2765 *
2766 * @attr ref android.R.styleable#TextView_linksClickable
2767 */
2768 public final boolean getLinksClickable() {
2769 return mLinksClickable;
2770 }
2771
2772 /**
2773 * Returns the list of URLSpans attached to the text
2774 * (by {@link Linkify} or otherwise) if any. You can call
2775 * {@link URLSpan#getURL} on them to find where they link to
2776 * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
2777 * to find the region of the text they are attached to.
2778 */
2779 public URLSpan[] getUrls() {
2780 if (mText instanceof Spanned) {
2781 return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
2782 } else {
2783 return new URLSpan[0];
2784 }
2785 }
2786
2787 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002788 * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this
2789 * TextView.
2790 *
2791 * @see #setHintTextColor(ColorStateList)
2792 * @see #getHintTextColors()
2793 * @see #setTextColor(int)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002794 *
2795 * @attr ref android.R.styleable#TextView_textColorHint
2796 */
2797 @android.view.RemotableViewMethod
2798 public final void setHintTextColor(int color) {
2799 mHintTextColor = ColorStateList.valueOf(color);
2800 updateTextColors();
2801 }
2802
2803 /**
2804 * Sets the color of the hint text.
2805 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002806 * @see #getHintTextColors()
2807 * @see #setHintTextColor(int)
2808 * @see #setTextColor(ColorStateList)
2809 * @see #setLinkTextColor(ColorStateList)
2810 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002811 * @attr ref android.R.styleable#TextView_textColorHint
2812 */
2813 public final void setHintTextColor(ColorStateList colors) {
2814 mHintTextColor = colors;
2815 updateTextColors();
2816 }
2817
2818 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002819 * @return the color of the hint text, for the different states of this TextView.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002820 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002821 * @see #setHintTextColor(ColorStateList)
2822 * @see #setHintTextColor(int)
2823 * @see #setTextColor(ColorStateList)
2824 * @see #setLinkTextColor(ColorStateList)
2825 *
2826 * @attr ref android.R.styleable#TextView_textColorHint
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002827 */
2828 public final ColorStateList getHintTextColors() {
2829 return mHintTextColor;
2830 }
2831
2832 /**
2833 * <p>Return the current color selected to paint the hint text.</p>
2834 *
2835 * @return Returns the current hint text color.
2836 */
2837 public final int getCurrentHintTextColor() {
2838 return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
2839 }
2840
2841 /**
2842 * Sets the color of links in the text.
2843 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002844 * @see #setLinkTextColor(ColorStateList)
2845 * @see #getLinkTextColors()
2846 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002847 * @attr ref android.R.styleable#TextView_textColorLink
2848 */
2849 @android.view.RemotableViewMethod
2850 public final void setLinkTextColor(int color) {
2851 mLinkTextColor = ColorStateList.valueOf(color);
2852 updateTextColors();
2853 }
2854
2855 /**
2856 * Sets the color of links in the text.
2857 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002858 * @see #setLinkTextColor(int)
2859 * @see #getLinkTextColors()
2860 * @see #setTextColor(ColorStateList)
2861 * @see #setHintTextColor(ColorStateList)
2862 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002863 * @attr ref android.R.styleable#TextView_textColorLink
2864 */
2865 public final void setLinkTextColor(ColorStateList colors) {
2866 mLinkTextColor = colors;
2867 updateTextColors();
2868 }
2869
2870 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002871 * @return the list of colors used to paint the links in the text, for the different states of
2872 * this TextView
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002873 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002874 * @see #setLinkTextColor(ColorStateList)
2875 * @see #setLinkTextColor(int)
2876 *
2877 * @attr ref android.R.styleable#TextView_textColorLink
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002878 */
2879 public final ColorStateList getLinkTextColors() {
2880 return mLinkTextColor;
2881 }
2882
2883 /**
2884 * Sets the horizontal alignment of the text and the
2885 * vertical gravity that will be used when there is extra space
2886 * in the TextView beyond what is required for the text itself.
2887 *
2888 * @see android.view.Gravity
2889 * @attr ref android.R.styleable#TextView_gravity
2890 */
2891 public void setGravity(int gravity) {
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07002892 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
Fabrice Di Meglio9e3b0022011-06-06 16:30:29 -07002893 gravity |= Gravity.START;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002894 }
2895 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
2896 gravity |= Gravity.TOP;
2897 }
2898
2899 boolean newLayout = false;
2900
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07002901 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) !=
2902 (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002903 newLayout = true;
2904 }
2905
2906 if (gravity != mGravity) {
2907 invalidate();
2908 }
2909
2910 mGravity = gravity;
2911
2912 if (mLayout != null && newLayout) {
2913 // XXX this is heavy-handed because no actual content changes.
2914 int want = mLayout.getWidth();
2915 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
2916
2917 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
2918 mRight - mLeft - getCompoundPaddingLeft() -
2919 getCompoundPaddingRight(), true);
2920 }
2921 }
2922
2923 /**
2924 * Returns the horizontal and vertical alignment of this TextView.
2925 *
2926 * @see android.view.Gravity
2927 * @attr ref android.R.styleable#TextView_gravity
2928 */
2929 public int getGravity() {
2930 return mGravity;
2931 }
2932
2933 /**
2934 * @return the flags on the Paint being used to display the text.
2935 * @see Paint#getFlags
2936 */
2937 public int getPaintFlags() {
2938 return mTextPaint.getFlags();
2939 }
2940
2941 /**
2942 * Sets flags on the Paint being used to display the text and
2943 * reflows the text if they are different from the old flags.
2944 * @see Paint#setFlags
2945 */
2946 @android.view.RemotableViewMethod
2947 public void setPaintFlags(int flags) {
2948 if (mTextPaint.getFlags() != flags) {
2949 mTextPaint.setFlags(flags);
2950
2951 if (mLayout != null) {
2952 nullLayouts();
2953 requestLayout();
2954 invalidate();
2955 }
2956 }
2957 }
2958
2959 /**
2960 * Sets whether the text should be allowed to be wider than the
2961 * View is. If false, it will be wrapped to the width of the View.
2962 *
2963 * @attr ref android.R.styleable#TextView_scrollHorizontally
2964 */
2965 public void setHorizontallyScrolling(boolean whether) {
Gilles Debunne22378292011-08-12 10:38:52 -07002966 if (mHorizontallyScrolling != whether) {
2967 mHorizontallyScrolling = whether;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002968
Gilles Debunne22378292011-08-12 10:38:52 -07002969 if (mLayout != null) {
2970 nullLayouts();
2971 requestLayout();
2972 invalidate();
2973 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002974 }
2975 }
2976
2977 /**
Gilles Debunnef2a02012011-10-27 11:10:14 -07002978 * Returns whether the text is allowed to be wider than the View is.
2979 * If false, the text will be wrapped to the width of the View.
2980 *
2981 * @attr ref android.R.styleable#TextView_scrollHorizontally
2982 * @hide
2983 */
2984 public boolean getHorizontallyScrolling() {
2985 return mHorizontallyScrolling;
2986 }
2987
2988 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08002989 * Makes the TextView at least this many lines tall.
2990 *
2991 * Setting this value overrides any other (minimum) height setting. A single line TextView will
2992 * set this value to 1.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002993 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002994 * @see #getMinLines()
2995 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002996 * @attr ref android.R.styleable#TextView_minLines
2997 */
2998 @android.view.RemotableViewMethod
2999 public void setMinLines(int minlines) {
3000 mMinimum = minlines;
3001 mMinMode = LINES;
3002
3003 requestLayout();
3004 invalidate();
3005 }
3006
3007 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003008 * @return the minimum number of lines displayed in this TextView, or -1 if the minimum
3009 * height was set in pixels instead using {@link #setMinHeight(int) or #setHeight(int)}.
3010 *
3011 * @see #setMinLines(int)
3012 *
3013 * @attr ref android.R.styleable#TextView_minLines
3014 */
3015 public int getMinLines() {
3016 return mMinMode == LINES ? mMinimum : -1;
3017 }
3018
3019 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003020 * Makes the TextView at least this many pixels tall.
3021 *
3022 * Setting this value overrides any other (minimum) number of lines setting.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003023 *
3024 * @attr ref android.R.styleable#TextView_minHeight
3025 */
3026 @android.view.RemotableViewMethod
3027 public void setMinHeight(int minHeight) {
3028 mMinimum = minHeight;
3029 mMinMode = PIXELS;
3030
3031 requestLayout();
3032 invalidate();
3033 }
3034
3035 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003036 * @return the minimum height of this TextView expressed in pixels, or -1 if the minimum
3037 * height was set in number of lines instead using {@link #setMinLines(int) or #setLines(int)}.
3038 *
3039 * @see #setMinHeight(int)
3040 *
3041 * @attr ref android.R.styleable#TextView_minHeight
3042 */
3043 public int getMinHeight() {
3044 return mMinMode == PIXELS ? mMinimum : -1;
3045 }
3046
3047 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003048 * Makes the TextView at most this many lines tall.
3049 *
3050 * Setting this value overrides any other (maximum) height setting.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003051 *
3052 * @attr ref android.R.styleable#TextView_maxLines
3053 */
3054 @android.view.RemotableViewMethod
3055 public void setMaxLines(int maxlines) {
3056 mMaximum = maxlines;
3057 mMaxMode = LINES;
3058
3059 requestLayout();
3060 invalidate();
3061 }
3062
3063 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003064 * @return the maximum number of lines displayed in this TextView, or -1 if the maximum
3065 * height was set in pixels instead using {@link #setMaxHeight(int) or #setHeight(int)}.
3066 *
3067 * @see #setMaxLines(int)
3068 *
3069 * @attr ref android.R.styleable#TextView_maxLines
3070 */
3071 public int getMaxLines() {
3072 return mMaxMode == LINES ? mMaximum : -1;
3073 }
3074
3075 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003076 * Makes the TextView at most this many pixels tall. This option is mutually exclusive with the
3077 * {@link #setMaxLines(int)} method.
3078 *
3079 * Setting this value overrides any other (maximum) number of lines setting.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003080 *
3081 * @attr ref android.R.styleable#TextView_maxHeight
3082 */
3083 @android.view.RemotableViewMethod
3084 public void setMaxHeight(int maxHeight) {
3085 mMaximum = maxHeight;
3086 mMaxMode = PIXELS;
3087
3088 requestLayout();
3089 invalidate();
3090 }
3091
3092 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003093 * @return the maximum height of this TextView expressed in pixels, or -1 if the maximum
3094 * height was set in number of lines instead using {@link #setMaxLines(int) or #setLines(int)}.
3095 *
3096 * @see #setMaxHeight(int)
3097 *
3098 * @attr ref android.R.styleable#TextView_maxHeight
3099 */
3100 public int getMaxHeight() {
3101 return mMaxMode == PIXELS ? mMaximum : -1;
3102 }
3103
3104 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003105 * Makes the TextView exactly this many lines tall.
3106 *
3107 * Note that setting this value overrides any other (minimum / maximum) number of lines or
3108 * height setting. A single line TextView will set this value to 1.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003109 *
3110 * @attr ref android.R.styleable#TextView_lines
3111 */
3112 @android.view.RemotableViewMethod
3113 public void setLines(int lines) {
3114 mMaximum = mMinimum = lines;
3115 mMaxMode = mMinMode = LINES;
3116
3117 requestLayout();
3118 invalidate();
3119 }
3120
3121 /**
3122 * Makes the TextView exactly this many pixels tall.
3123 * You could do the same thing by specifying this number in the
3124 * LayoutParams.
3125 *
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003126 * Note that setting this value overrides any other (minimum / maximum) number of lines or
3127 * height setting.
3128 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003129 * @attr ref android.R.styleable#TextView_height
3130 */
3131 @android.view.RemotableViewMethod
3132 public void setHeight(int pixels) {
3133 mMaximum = mMinimum = pixels;
3134 mMaxMode = mMinMode = PIXELS;
3135
3136 requestLayout();
3137 invalidate();
3138 }
3139
3140 /**
3141 * Makes the TextView at least this many ems wide
3142 *
3143 * @attr ref android.R.styleable#TextView_minEms
3144 */
3145 @android.view.RemotableViewMethod
3146 public void setMinEms(int minems) {
3147 mMinWidth = minems;
3148 mMinWidthMode = EMS;
3149
3150 requestLayout();
3151 invalidate();
3152 }
3153
3154 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003155 * @return the minimum width of the TextView, expressed in ems or -1 if the minimum width
3156 * was set in pixels instead (using {@link #setMinWidth(int)} or {@link #setWidth(int)}).
3157 *
3158 * @see #setMinEms(int)
3159 * @see #setEms(int)
3160 *
3161 * @attr ref android.R.styleable#TextView_minEms
3162 */
3163 public int getMinEms() {
3164 return mMinWidthMode == EMS ? mMinWidth : -1;
3165 }
3166
3167 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003168 * Makes the TextView at least this many pixels wide
3169 *
3170 * @attr ref android.R.styleable#TextView_minWidth
3171 */
3172 @android.view.RemotableViewMethod
3173 public void setMinWidth(int minpixels) {
3174 mMinWidth = minpixels;
3175 mMinWidthMode = PIXELS;
3176
3177 requestLayout();
3178 invalidate();
3179 }
3180
3181 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003182 * @return the minimum width of the TextView, in pixels or -1 if the minimum width
3183 * was set in ems instead (using {@link #setMinEms(int)} or {@link #setEms(int)}).
3184 *
3185 * @see #setMinWidth(int)
3186 * @see #setWidth(int)
3187 *
3188 * @attr ref android.R.styleable#TextView_minWidth
3189 */
3190 public int getMinWidth() {
3191 return mMinWidthMode == PIXELS ? mMinWidth : -1;
3192 }
3193
3194 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003195 * Makes the TextView at most this many ems wide
3196 *
3197 * @attr ref android.R.styleable#TextView_maxEms
3198 */
3199 @android.view.RemotableViewMethod
3200 public void setMaxEms(int maxems) {
3201 mMaxWidth = maxems;
3202 mMaxWidthMode = EMS;
3203
3204 requestLayout();
3205 invalidate();
3206 }
3207
3208 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003209 * @return the maximum width of the TextView, expressed in ems or -1 if the maximum width
3210 * was set in pixels instead (using {@link #setMaxWidth(int)} or {@link #setWidth(int)}).
3211 *
3212 * @see #setMaxEms(int)
3213 * @see #setEms(int)
3214 *
3215 * @attr ref android.R.styleable#TextView_maxEms
3216 */
3217 public int getMaxEms() {
3218 return mMaxWidthMode == EMS ? mMaxWidth : -1;
3219 }
3220
3221 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003222 * Makes the TextView at most this many pixels wide
3223 *
3224 * @attr ref android.R.styleable#TextView_maxWidth
3225 */
3226 @android.view.RemotableViewMethod
3227 public void setMaxWidth(int maxpixels) {
3228 mMaxWidth = maxpixels;
3229 mMaxWidthMode = PIXELS;
3230
3231 requestLayout();
3232 invalidate();
3233 }
3234
3235 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003236 * @return the maximum width of the TextView, in pixels or -1 if the maximum width
3237 * was set in ems instead (using {@link #setMaxEms(int)} or {@link #setEms(int)}).
3238 *
3239 * @see #setMaxWidth(int)
3240 * @see #setWidth(int)
3241 *
3242 * @attr ref android.R.styleable#TextView_maxWidth
3243 */
3244 public int getMaxWidth() {
3245 return mMaxWidthMode == PIXELS ? mMaxWidth : -1;
3246 }
3247
3248 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003249 * Makes the TextView exactly this many ems wide
3250 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003251 * @see #setMaxEms(int)
3252 * @see #setMinEms(int)
3253 * @see #getMinEms()
3254 * @see #getMaxEms()
3255 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003256 * @attr ref android.R.styleable#TextView_ems
3257 */
3258 @android.view.RemotableViewMethod
3259 public void setEms(int ems) {
3260 mMaxWidth = mMinWidth = ems;
3261 mMaxWidthMode = mMinWidthMode = EMS;
3262
3263 requestLayout();
3264 invalidate();
3265 }
3266
3267 /**
3268 * Makes the TextView exactly this many pixels wide.
3269 * You could do the same thing by specifying this number in the
3270 * LayoutParams.
3271 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003272 * @see #setMaxWidth(int)
3273 * @see #setMinWidth(int)
3274 * @see #getMinWidth()
3275 * @see #getMaxWidth()
3276 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003277 * @attr ref android.R.styleable#TextView_width
3278 */
3279 @android.view.RemotableViewMethod
3280 public void setWidth(int pixels) {
3281 mMaxWidth = mMinWidth = pixels;
3282 mMaxWidthMode = mMinWidthMode = PIXELS;
3283
3284 requestLayout();
3285 invalidate();
3286 }
3287
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003288 /**
3289 * Sets line spacing for this TextView. Each line will have its height
3290 * multiplied by <code>mult</code> and have <code>add</code> added to it.
3291 *
3292 * @attr ref android.R.styleable#TextView_lineSpacingExtra
3293 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
3294 */
3295 public void setLineSpacing(float add, float mult) {
Gilles Debunne22378292011-08-12 10:38:52 -07003296 if (mSpacingAdd != add || mSpacingMult != mult) {
3297 mSpacingAdd = add;
3298 mSpacingMult = mult;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003299
Gilles Debunne22378292011-08-12 10:38:52 -07003300 if (mLayout != null) {
3301 nullLayouts();
3302 requestLayout();
3303 invalidate();
3304 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003305 }
3306 }
3307
3308 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003309 * Gets the line spacing multiplier
3310 *
3311 * @return the value by which each line's height is multiplied to get its actual height.
3312 *
3313 * @see #setLineSpacing(float, float)
3314 * @see #getLineSpacingExtra()
3315 *
3316 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
3317 */
3318 public float getLineSpacingMultiplier() {
3319 return mSpacingMult;
3320 }
3321
3322 /**
3323 * Gets the line spacing extra space
3324 *
3325 * @return the extra space that is added to the height of each lines of this TextView.
3326 *
3327 * @see #setLineSpacing(float, float)
3328 * @see #getLineSpacingMultiplier()
3329 *
3330 * @attr ref android.R.styleable#TextView_lineSpacingExtra
3331 */
3332 public float getLineSpacingExtra() {
3333 return mSpacingAdd;
3334 }
3335
3336 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003337 * Convenience method: Append the specified text to the TextView's
3338 * display buffer, upgrading it to BufferType.EDITABLE if it was
3339 * not already editable.
3340 */
3341 public final void append(CharSequence text) {
3342 append(text, 0, text.length());
3343 }
3344
3345 /**
3346 * Convenience method: Append the specified text slice to the TextView's
3347 * display buffer, upgrading it to BufferType.EDITABLE if it was
3348 * not already editable.
3349 */
3350 public void append(CharSequence text, int start, int end) {
3351 if (!(mText instanceof Editable)) {
3352 setText(mText, BufferType.EDITABLE);
3353 }
3354
3355 ((Editable) mText).append(text, start, end);
3356 }
3357
3358 private void updateTextColors() {
3359 boolean inval = false;
3360 int color = mTextColor.getColorForState(getDrawableState(), 0);
3361 if (color != mCurTextColor) {
3362 mCurTextColor = color;
3363 inval = true;
3364 }
3365 if (mLinkTextColor != null) {
3366 color = mLinkTextColor.getColorForState(getDrawableState(), 0);
3367 if (color != mTextPaint.linkColor) {
3368 mTextPaint.linkColor = color;
3369 inval = true;
3370 }
3371 }
3372 if (mHintTextColor != null) {
3373 color = mHintTextColor.getColorForState(getDrawableState(), 0);
3374 if (color != mCurHintTextColor && mText.length() == 0) {
3375 mCurHintTextColor = color;
3376 inval = true;
3377 }
3378 }
3379 if (inval) {
Gilles Debunne33b7de852012-03-12 11:57:48 -07003380 // Text needs to be redrawn with the new color
Gilles Debunne2d373a12012-04-20 15:32:19 -07003381 if (mEditor != null) mEditor.invalidateTextDisplayList();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003382 invalidate();
3383 }
3384 }
3385
3386 @Override
3387 protected void drawableStateChanged() {
3388 super.drawableStateChanged();
3389 if (mTextColor != null && mTextColor.isStateful()
3390 || (mHintTextColor != null && mHintTextColor.isStateful())
3391 || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
3392 updateTextColors();
3393 }
3394
3395 final Drawables dr = mDrawables;
3396 if (dr != null) {
3397 int[] state = getDrawableState();
3398 if (dr.mDrawableTop != null && dr.mDrawableTop.isStateful()) {
3399 dr.mDrawableTop.setState(state);
3400 }
3401 if (dr.mDrawableBottom != null && dr.mDrawableBottom.isStateful()) {
3402 dr.mDrawableBottom.setState(state);
3403 }
3404 if (dr.mDrawableLeft != null && dr.mDrawableLeft.isStateful()) {
3405 dr.mDrawableLeft.setState(state);
3406 }
3407 if (dr.mDrawableRight != null && dr.mDrawableRight.isStateful()) {
3408 dr.mDrawableRight.setState(state);
3409 }
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07003410 if (dr.mDrawableStart != null && dr.mDrawableStart.isStateful()) {
3411 dr.mDrawableStart.setState(state);
3412 }
3413 if (dr.mDrawableEnd != null && dr.mDrawableEnd.isStateful()) {
3414 dr.mDrawableEnd.setState(state);
3415 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003416 }
3417 }
3418
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003419 @Override
3420 public Parcelable onSaveInstanceState() {
3421 Parcelable superState = super.onSaveInstanceState();
3422
3423 // Save state if we are forced to
3424 boolean save = mFreezesText;
3425 int start = 0;
3426 int end = 0;
3427
3428 if (mText != null) {
Gilles Debunne05336272010-07-09 20:13:45 -07003429 start = getSelectionStart();
3430 end = getSelectionEnd();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003431 if (start >= 0 || end >= 0) {
3432 // Or save state if there is a selection
3433 save = true;
3434 }
3435 }
3436
3437 if (save) {
3438 SavedState ss = new SavedState(superState);
3439 // XXX Should also save the current scroll position!
3440 ss.selStart = start;
3441 ss.selEnd = end;
3442
3443 if (mText instanceof Spanned) {
3444 /*
3445 * Calling setText() strips off any ChangeWatchers;
3446 * strip them now to avoid leaking references.
3447 * But do it to a copy so that if there are any
3448 * further changes to the text of this view, it
3449 * won't get into an inconsistent state.
3450 */
3451
3452 Spannable sp = new SpannableString(mText);
3453
Gilles Debunne176cd0d2011-09-29 16:37:27 -07003454 for (ChangeWatcher cw : sp.getSpans(0, sp.length(), ChangeWatcher.class)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003455 sp.removeSpan(cw);
3456 }
3457
Gilles Debunne60e21862012-01-30 15:04:14 -08003458 if (mEditor != null) {
3459 removeMisspelledSpans(sp);
Gilles Debunne2d373a12012-04-20 15:32:19 -07003460 sp.removeSpan(mEditor.mSuggestionRangeSpan);
Gilles Debunne60e21862012-01-30 15:04:14 -08003461 }
Gilles Debunneaa67eef2011-06-01 18:03:37 -07003462
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003463 ss.text = sp;
3464 } else {
3465 ss.text = mText.toString();
3466 }
3467
3468 if (isFocused() && start >= 0 && end >= 0) {
3469 ss.frozenWithFocus = true;
3470 }
3471
Gilles Debunne60e21862012-01-30 15:04:14 -08003472 ss.error = getError();
The Android Open Source Project4df24232009-03-05 14:34:35 -08003473
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003474 return ss;
3475 }
3476
3477 return superState;
3478 }
3479
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07003480 void removeMisspelledSpans(Spannable spannable) {
3481 SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(),
3482 SuggestionSpan.class);
3483 for (int i = 0; i < suggestionSpans.length; i++) {
3484 int flags = suggestionSpans[i].getFlags();
3485 if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
3486 && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
3487 spannable.removeSpan(suggestionSpans[i]);
3488 }
3489 }
3490 }
3491
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003492 @Override
3493 public void onRestoreInstanceState(Parcelable state) {
3494 if (!(state instanceof SavedState)) {
3495 super.onRestoreInstanceState(state);
3496 return;
3497 }
3498
3499 SavedState ss = (SavedState)state;
3500 super.onRestoreInstanceState(ss.getSuperState());
3501
3502 // XXX restore buffer type too, as well as lots of other stuff
3503 if (ss.text != null) {
3504 setText(ss.text);
3505 }
3506
3507 if (ss.selStart >= 0 && ss.selEnd >= 0) {
3508 if (mText instanceof Spannable) {
3509 int len = mText.length();
3510
3511 if (ss.selStart > len || ss.selEnd > len) {
3512 String restored = "";
3513
3514 if (ss.text != null) {
3515 restored = "(restored) ";
3516 }
3517
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07003518 Log.e(LOG_TAG, "Saved cursor position " + ss.selStart +
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003519 "/" + ss.selEnd + " out of range for " + restored +
3520 "text " + mText);
3521 } else {
Gilles Debunnec1e79b42012-02-24 17:29:31 -08003522 Selection.setSelection((Spannable) mText, ss.selStart, ss.selEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003523
3524 if (ss.frozenWithFocus) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07003525 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07003526 mEditor.mFrozenWithFocus = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003527 }
3528 }
3529 }
3530 }
The Android Open Source Project4df24232009-03-05 14:34:35 -08003531
3532 if (ss.error != null) {
Romain Guy9bc9fa12009-07-21 16:57:29 -07003533 final CharSequence error = ss.error;
3534 // Display the error later, after the first layout pass
3535 post(new Runnable() {
3536 public void run() {
3537 setError(error);
3538 }
3539 });
The Android Open Source Project4df24232009-03-05 14:34:35 -08003540 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003541 }
3542
3543 /**
3544 * Control whether this text view saves its entire text contents when
3545 * freezing to an icicle, in addition to dynamic state such as cursor
3546 * position. By default this is false, not saving the text. Set to true
3547 * if the text in the text view is not being saved somewhere else in
3548 * persistent storage (such as in a content provider) so that if the
3549 * view is later thawed the user will not lose their data.
3550 *
3551 * @param freezesText Controls whether a frozen icicle should include the
3552 * entire text data: true to include it, false to not.
3553 *
3554 * @attr ref android.R.styleable#TextView_freezesText
3555 */
3556 @android.view.RemotableViewMethod
3557 public void setFreezesText(boolean freezesText) {
3558 mFreezesText = freezesText;
3559 }
3560
3561 /**
3562 * Return whether this text view is including its entire text contents
3563 * in frozen icicles.
3564 *
3565 * @return Returns true if text is included, false if it isn't.
3566 *
3567 * @see #setFreezesText
3568 */
3569 public boolean getFreezesText() {
3570 return mFreezesText;
3571 }
3572
3573 ///////////////////////////////////////////////////////////////////////////
3574
3575 /**
3576 * Sets the Factory used to create new Editables.
3577 */
3578 public final void setEditableFactory(Editable.Factory factory) {
3579 mEditableFactory = factory;
3580 setText(mText);
3581 }
3582
3583 /**
3584 * Sets the Factory used to create new Spannables.
3585 */
3586 public final void setSpannableFactory(Spannable.Factory factory) {
3587 mSpannableFactory = factory;
3588 setText(mText);
3589 }
3590
3591 /**
3592 * Sets the string value of the TextView. TextView <em>does not</em> accept
3593 * HTML-like formatting, which you can do with text strings in XML resource files.
3594 * To style your strings, attach android.text.style.* objects to a
3595 * {@link android.text.SpannableString SpannableString}, or see the
3596 * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
Gilles Debunne21078e42011-08-02 10:22:35 -07003597 * Available Resource Types</a> documentation for an example of setting
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003598 * formatted text in the XML resource file.
3599 *
3600 * @attr ref android.R.styleable#TextView_text
3601 */
3602 @android.view.RemotableViewMethod
3603 public final void setText(CharSequence text) {
3604 setText(text, mBufferType);
3605 }
3606
3607 /**
3608 * Like {@link #setText(CharSequence)},
3609 * except that the cursor position (if any) is retained in the new text.
3610 *
3611 * @param text The new text to place in the text view.
3612 *
3613 * @see #setText(CharSequence)
3614 */
3615 @android.view.RemotableViewMethod
3616 public final void setTextKeepState(CharSequence text) {
3617 setTextKeepState(text, mBufferType);
3618 }
3619
3620 /**
3621 * Sets the text that this TextView is to display (see
3622 * {@link #setText(CharSequence)}) and also sets whether it is stored
3623 * in a styleable/spannable buffer and whether it is editable.
3624 *
3625 * @attr ref android.R.styleable#TextView_text
3626 * @attr ref android.R.styleable#TextView_bufferType
3627 */
3628 public void setText(CharSequence text, BufferType type) {
3629 setText(text, type, true, 0);
3630
3631 if (mCharWrapper != null) {
3632 mCharWrapper.mChars = null;
3633 }
3634 }
3635
3636 private void setText(CharSequence text, BufferType type,
3637 boolean notifyBefore, int oldlen) {
3638 if (text == null) {
3639 text = "";
3640 }
3641
Luca Zanoline0760452011-09-08 12:03:37 +01003642 // If suggestions are not enabled, remove the suggestion spans from the text
3643 if (!isSuggestionsEnabled()) {
3644 text = removeSuggestionSpans(text);
3645 }
3646
Romain Guy939151f2009-04-08 14:22:40 -07003647 if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
3648
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003649 if (text instanceof Spanned &&
3650 ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
Adam Powell282e3772011-08-30 16:51:11 -07003651 if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {
3652 setHorizontalFadingEdgeEnabled(true);
3653 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
3654 } else {
3655 setHorizontalFadingEdgeEnabled(false);
3656 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
3657 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003658 setEllipsize(TextUtils.TruncateAt.MARQUEE);
3659 }
3660
3661 int n = mFilters.length;
3662 for (int i = 0; i < n; i++) {
Gilles Debunnec1714022012-01-17 13:59:23 -08003663 CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003664 if (out != null) {
3665 text = out;
3666 }
3667 }
3668
3669 if (notifyBefore) {
3670 if (mText != null) {
3671 oldlen = mText.length();
3672 sendBeforeTextChanged(mText, 0, oldlen, text.length());
3673 } else {
3674 sendBeforeTextChanged("", 0, 0, text.length());
3675 }
3676 }
3677
3678 boolean needEditableForNotification = false;
3679
3680 if (mListeners != null && mListeners.size() != 0) {
3681 needEditableForNotification = true;
3682 }
3683
Gilles Debunne2d373a12012-04-20 15:32:19 -07003684 if (type == BufferType.EDITABLE || getKeyListener() != null ||
3685 needEditableForNotification) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07003686 createEditorIfNeeded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003687 Editable t = mEditableFactory.newEditable(text);
3688 text = t;
3689 setFilters(t, mFilters);
3690 InputMethodManager imm = InputMethodManager.peekInstance();
3691 if (imm != null) imm.restartInput(this);
3692 } else if (type == BufferType.SPANNABLE || mMovement != null) {
3693 text = mSpannableFactory.newSpannable(text);
3694 } else if (!(text instanceof CharWrapper)) {
3695 text = TextUtils.stringOrSpannedString(text);
3696 }
3697
3698 if (mAutoLinkMask != 0) {
3699 Spannable s2;
3700
3701 if (type == BufferType.EDITABLE || text instanceof Spannable) {
3702 s2 = (Spannable) text;
3703 } else {
3704 s2 = mSpannableFactory.newSpannable(text);
3705 }
3706
3707 if (Linkify.addLinks(s2, mAutoLinkMask)) {
3708 text = s2;
3709 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
3710
3711 /*
3712 * We must go ahead and set the text before changing the
3713 * movement method, because setMovementMethod() may call
3714 * setText() again to try to upgrade the buffer type.
3715 */
3716 mText = text;
3717
Gilles Debunnecbcb3452010-12-17 15:31:02 -08003718 // Do not change the movement method for text that support text selection as it
3719 // would prevent an arbitrary cursor displacement.
Gilles Debunnebb588da2011-07-11 18:26:19 -07003720 if (mLinksClickable && !textCanBeSelected()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003721 setMovementMethod(LinkMovementMethod.getInstance());
3722 }
3723 }
3724 }
3725
3726 mBufferType = type;
3727 mText = text;
3728
Adam Powell7f8f79a2011-07-07 18:35:54 -07003729 if (mTransformation == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003730 mTransformed = text;
Adam Powell7f8f79a2011-07-07 18:35:54 -07003731 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003732 mTransformed = mTransformation.getTransformation(text, this);
Adam Powell7f8f79a2011-07-07 18:35:54 -07003733 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003734
3735 final int textLength = text.length();
3736
Adam Powell7f8f79a2011-07-07 18:35:54 -07003737 if (text instanceof Spannable && !mAllowTransformationLengthChange) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003738 Spannable sp = (Spannable) text;
3739
Gilles Debunnec62589c2012-04-12 14:50:23 -07003740 // Remove any ChangeWatchers that might have come from other TextViews.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003741 final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
3742 final int count = watchers.length;
Gilles Debunnec62589c2012-04-12 14:50:23 -07003743 for (int i = 0; i < count; i++) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003744 sp.removeSpan(watchers[i]);
Gilles Debunnec62589c2012-04-12 14:50:23 -07003745 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003746
Gilles Debunnec62589c2012-04-12 14:50:23 -07003747 if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003748
3749 sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE |
Gilles Debunne60e21862012-01-30 15:04:14 -08003750 (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003751
Gilles Debunnec62589c2012-04-12 14:50:23 -07003752 if (mEditor != null) mEditor.addSpanWatchers(sp);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003753
3754 if (mTransformation != null) {
3755 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003756 }
3757
3758 if (mMovement != null) {
3759 mMovement.initialize(this, (Spannable) text);
3760
3761 /*
3762 * Initializing the movement method will have set the
3763 * selection, so reset mSelectionMoved to keep that from
3764 * interfering with the normal on-focus selection-setting.
3765 */
Gilles Debunne2d373a12012-04-20 15:32:19 -07003766 if (mEditor != null) mEditor.mSelectionMoved = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003767 }
3768 }
3769
3770 if (mLayout != null) {
3771 checkForRelayout();
3772 }
3773
3774 sendOnTextChanged(text, 0, oldlen, textLength);
3775 onTextChanged(text, 0, oldlen, textLength);
3776
3777 if (needEditableForNotification) {
3778 sendAfterTextChanged((Editable) text);
3779 }
Gilles Debunne05336272010-07-09 20:13:45 -07003780
Gilles Debunnebaaace52010-10-01 15:47:13 -07003781 // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
Gilles Debunne2d373a12012-04-20 15:32:19 -07003782 if (mEditor != null) mEditor.prepareCursorControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003783 }
3784
3785 /**
3786 * Sets the TextView to display the specified slice of the specified
3787 * char array. You must promise that you will not change the contents
3788 * of the array except for right before another call to setText(),
3789 * since the TextView has no way to know that the text
3790 * has changed and that it needs to invalidate and re-layout.
3791 */
3792 public final void setText(char[] text, int start, int len) {
3793 int oldlen = 0;
3794
3795 if (start < 0 || len < 0 || start + len > text.length) {
3796 throw new IndexOutOfBoundsException(start + ", " + len);
3797 }
3798
3799 /*
3800 * We must do the before-notification here ourselves because if
3801 * the old text is a CharWrapper we destroy it before calling
3802 * into the normal path.
3803 */
3804 if (mText != null) {
3805 oldlen = mText.length();
3806 sendBeforeTextChanged(mText, 0, oldlen, len);
3807 } else {
3808 sendBeforeTextChanged("", 0, 0, len);
3809 }
3810
3811 if (mCharWrapper == null) {
3812 mCharWrapper = new CharWrapper(text, start, len);
3813 } else {
3814 mCharWrapper.set(text, start, len);
3815 }
3816
3817 setText(mCharWrapper, mBufferType, false, oldlen);
3818 }
3819
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003820 /**
3821 * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)},
3822 * except that the cursor position (if any) is retained in the new text.
3823 *
3824 * @see #setText(CharSequence, android.widget.TextView.BufferType)
3825 */
3826 public final void setTextKeepState(CharSequence text, BufferType type) {
3827 int start = getSelectionStart();
3828 int end = getSelectionEnd();
3829 int len = text.length();
3830
3831 setText(text, type);
3832
3833 if (start >= 0 || end >= 0) {
3834 if (mText instanceof Spannable) {
3835 Selection.setSelection((Spannable) mText,
3836 Math.max(0, Math.min(start, len)),
3837 Math.max(0, Math.min(end, len)));
3838 }
3839 }
3840 }
3841
3842 @android.view.RemotableViewMethod
3843 public final void setText(int resid) {
3844 setText(getContext().getResources().getText(resid));
3845 }
3846
3847 public final void setText(int resid, BufferType type) {
3848 setText(getContext().getResources().getText(resid), type);
3849 }
3850
3851 /**
3852 * Sets the text to be displayed when the text of the TextView is empty.
3853 * Null means to use the normal empty text. The hint does not currently
3854 * participate in determining the size of the view.
3855 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003856 * @attr ref android.R.styleable#TextView_hint
3857 */
3858 @android.view.RemotableViewMethod
3859 public final void setHint(CharSequence hint) {
3860 mHint = TextUtils.stringOrSpannedString(hint);
3861
3862 if (mLayout != null) {
3863 checkForRelayout();
3864 }
3865
Romain Guy4dc4f732009-06-19 15:16:40 -07003866 if (mText.length() == 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003867 invalidate();
Romain Guy4dc4f732009-06-19 15:16:40 -07003868 }
Gilles Debunne626c3162012-02-14 15:46:41 -08003869
Gilles Debunne33b7de852012-03-12 11:57:48 -07003870 // Invalidate display list if hint is currently used
Gilles Debunne60e21862012-01-30 15:04:14 -08003871 if (mEditor != null && mText.length() == 0 && mHint != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07003872 mEditor.invalidateTextDisplayList();
Gilles Debunne60e21862012-01-30 15:04:14 -08003873 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003874 }
3875
3876 /**
3877 * Sets the text to be displayed when the text of the TextView is empty,
3878 * from a resource.
3879 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003880 * @attr ref android.R.styleable#TextView_hint
3881 */
3882 @android.view.RemotableViewMethod
3883 public final void setHint(int resid) {
3884 setHint(getContext().getResources().getText(resid));
3885 }
3886
3887 /**
3888 * Returns the hint that is displayed when the text of the TextView
3889 * is empty.
3890 *
3891 * @attr ref android.R.styleable#TextView_hint
3892 */
3893 @ViewDebug.CapturedViewProperty
3894 public CharSequence getHint() {
3895 return mHint;
3896 }
3897
Gilles Debunned88876a2012-03-16 17:34:04 -07003898 boolean isSingleLine() {
3899 return mSingleLine;
3900 }
3901
Gilles Debunne3784a7f2011-07-15 13:49:38 -07003902 private static boolean isMultilineInputType(int type) {
Gilles Debunne91a08cf2010-11-08 17:34:49 -08003903 return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) ==
3904 (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
3905 }
3906
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003907 /**
Gilles Debunned88876a2012-03-16 17:34:04 -07003908 * Removes the suggestion spans.
3909 */
3910 CharSequence removeSuggestionSpans(CharSequence text) {
3911 if (text instanceof Spanned) {
3912 Spannable spannable;
3913 if (text instanceof Spannable) {
3914 spannable = (Spannable) text;
3915 } else {
3916 spannable = new SpannableString(text);
3917 text = spannable;
3918 }
3919
3920 SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
3921 for (int i = 0; i < spans.length; i++) {
3922 spannable.removeSpan(spans[i]);
3923 }
3924 }
3925 return text;
3926 }
3927
3928 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003929 * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This
3930 * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)},
3931 * to match the given content type. If the given content type is {@link EditorInfo#TYPE_NULL}
3932 * then a soft keyboard will not be displayed for this text view.
3933 *
3934 * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be
3935 * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input
3936 * type.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003937 *
3938 * @see #getInputType()
3939 * @see #setRawInputType(int)
3940 * @see android.text.InputType
3941 * @attr ref android.R.styleable#TextView_inputType
3942 */
3943 public void setInputType(int type) {
Gilles Debunne60e21862012-01-30 15:04:14 -08003944 final boolean wasPassword = isPasswordInputType(getInputType());
3945 final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003946 setInputType(type, false);
Bjorn Bringertad8da912009-09-17 10:47:35 +01003947 final boolean isPassword = isPasswordInputType(type);
3948 final boolean isVisiblePassword = isVisiblePasswordInputType(type);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003949 boolean forceUpdate = false;
3950 if (isPassword) {
3951 setTransformationMethod(PasswordTransformationMethod.getInstance());
Raph Leviend570e892012-05-09 11:45:34 -07003952 setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
Bjorn Bringertad8da912009-09-17 10:47:35 +01003953 } else if (isVisiblePassword) {
Amith Yamasania8c0edb2009-09-27 16:51:21 -07003954 if (mTransformation == PasswordTransformationMethod.getInstance()) {
3955 forceUpdate = true;
3956 }
Raph Leviend570e892012-05-09 11:45:34 -07003957 setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
Bjorn Bringertad8da912009-09-17 10:47:35 +01003958 } else if (wasPassword || wasVisiblePassword) {
3959 // not in password mode, clean up typeface and transformation
Raph Leviend570e892012-05-09 11:45:34 -07003960 setTypefaceFromAttrs(null /* fontFamily */, -1, -1);
Bjorn Bringertad8da912009-09-17 10:47:35 +01003961 if (mTransformation == PasswordTransformationMethod.getInstance()) {
3962 forceUpdate = true;
3963 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003964 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07003965
Gilles Debunne91a08cf2010-11-08 17:34:49 -08003966 boolean singleLine = !isMultilineInputType(type);
Gilles Debunne2d373a12012-04-20 15:32:19 -07003967
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003968 // We need to update the single line mode if it has changed or we
3969 // were previously in password mode.
Gilles Debunne91a08cf2010-11-08 17:34:49 -08003970 if (mSingleLine != singleLine || forceUpdate) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003971 // Change single line mode, but only change the transformation if
3972 // we are not in password mode.
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003973 applySingleLine(singleLine, !isPassword, true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003974 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07003975
Luca Zanoline0760452011-09-08 12:03:37 +01003976 if (!isSuggestionsEnabled()) {
3977 mText = removeSuggestionSpans(mText);
3978 }
3979
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003980 InputMethodManager imm = InputMethodManager.peekInstance();
3981 if (imm != null) imm.restartInput(this);
3982 }
3983
Gilles Debunne0dcad2b2010-10-15 16:29:25 -07003984 /**
3985 * It would be better to rely on the input type for everything. A password inputType should have
3986 * a password transformation. We should hence use isPasswordInputType instead of this method.
3987 *
3988 * We should:
3989 * - Call setInputType in setKeyListener instead of changing the input type directly (which
3990 * would install the correct transformation).
3991 * - Refuse the installation of a non-password transformation in setTransformation if the input
3992 * type is password.
3993 *
3994 * However, this is like this for legacy reasons and we cannot break existing apps. This method
3995 * is useful since it matches what the user can see (obfuscated text or not).
3996 *
3997 * @return true if the current transformation method is of the password type.
3998 */
3999 private boolean hasPasswordTransformationMethod() {
4000 return mTransformation instanceof PasswordTransformationMethod;
4001 }
4002
Gilles Debunne3784a7f2011-07-15 13:49:38 -07004003 private static boolean isPasswordInputType(int inputType) {
Gilles Debunned7483bf2010-11-10 10:47:45 -08004004 final int variation =
4005 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004006 return variation
Gilles Debunned7483bf2010-11-10 10:47:45 -08004007 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)
4008 || variation
Ken Wakasa82d731a2010-12-24 23:42:41 +09004009 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD)
4010 || variation
4011 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004012 }
4013
Gilles Debunne3784a7f2011-07-15 13:49:38 -07004014 private static boolean isVisiblePasswordInputType(int inputType) {
Gilles Debunned7483bf2010-11-10 10:47:45 -08004015 final int variation =
4016 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004017 return variation
Gilles Debunned7483bf2010-11-10 10:47:45 -08004018 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004019 }
4020
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004021 /**
4022 * Directly change the content type integer of the text view, without
4023 * modifying any other state.
4024 * @see #setInputType(int)
4025 * @see android.text.InputType
4026 * @attr ref android.R.styleable#TextView_inputType
4027 */
4028 public void setRawInputType(int type) {
Gilles Debunne60e21862012-01-30 15:04:14 -08004029 if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
Gilles Debunne5fae9962012-05-08 14:53:20 -07004030 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004031 mEditor.mInputType = type;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004032 }
4033
4034 private void setInputType(int type, boolean direct) {
4035 final int cls = type & EditorInfo.TYPE_MASK_CLASS;
4036 KeyListener input;
4037 if (cls == EditorInfo.TYPE_CLASS_TEXT) {
Gilles Debunnee67b58a2010-08-31 15:55:31 -07004038 boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004039 TextKeyListener.Capitalize cap;
4040 if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
4041 cap = TextKeyListener.Capitalize.CHARACTERS;
4042 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
4043 cap = TextKeyListener.Capitalize.WORDS;
4044 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
4045 cap = TextKeyListener.Capitalize.SENTENCES;
4046 } else {
4047 cap = TextKeyListener.Capitalize.NONE;
4048 }
4049 input = TextKeyListener.getInstance(autotext, cap);
4050 } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
4051 input = DigitsKeyListener.getInstance(
4052 (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
4053 (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
4054 } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
4055 switch (type & EditorInfo.TYPE_MASK_VARIATION) {
4056 case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
4057 input = DateKeyListener.getInstance();
4058 break;
4059 case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
4060 input = TimeKeyListener.getInstance();
4061 break;
4062 default:
4063 input = DateTimeKeyListener.getInstance();
4064 break;
4065 }
4066 } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
4067 input = DialerKeyListener.getInstance();
4068 } else {
4069 input = TextKeyListener.getInstance();
4070 }
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07004071 setRawInputType(type);
Gilles Debunne60e21862012-01-30 15:04:14 -08004072 if (direct) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004073 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004074 mEditor.mKeyListener = input;
Gilles Debunne60e21862012-01-30 15:04:14 -08004075 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004076 setKeyListenerOnly(input);
4077 }
4078 }
4079
4080 /**
Gilles Debunne60e21862012-01-30 15:04:14 -08004081 * Get the type of the editable content.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004082 *
4083 * @see #setInputType(int)
4084 * @see android.text.InputType
4085 */
4086 public int getInputType() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004087 return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004088 }
4089
4090 /**
4091 * Change the editor type integer associated with the text view, which
4092 * will be reported to an IME with {@link EditorInfo#imeOptions} when it
4093 * has focus.
4094 * @see #getImeOptions
4095 * @see android.view.inputmethod.EditorInfo
4096 * @attr ref android.R.styleable#TextView_imeOptions
4097 */
4098 public void setImeOptions(int imeOptions) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004099 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004100 mEditor.createInputContentTypeIfNeeded();
4101 mEditor.mInputContentType.imeOptions = imeOptions;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004102 }
4103
4104 /**
4105 * Get the type of the IME editor.
4106 *
4107 * @see #setImeOptions(int)
4108 * @see android.view.inputmethod.EditorInfo
4109 */
4110 public int getImeOptions() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004111 return mEditor != null && mEditor.mInputContentType != null
4112 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004113 }
4114
4115 /**
4116 * Change the custom IME action associated with the text view, which
4117 * will be reported to an IME with {@link EditorInfo#actionLabel}
4118 * and {@link EditorInfo#actionId} when it has focus.
4119 * @see #getImeActionLabel
4120 * @see #getImeActionId
4121 * @see android.view.inputmethod.EditorInfo
4122 * @attr ref android.R.styleable#TextView_imeActionLabel
4123 * @attr ref android.R.styleable#TextView_imeActionId
4124 */
4125 public void setImeActionLabel(CharSequence label, int actionId) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004126 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004127 mEditor.createInputContentTypeIfNeeded();
4128 mEditor.mInputContentType.imeActionLabel = label;
4129 mEditor.mInputContentType.imeActionId = actionId;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004130 }
4131
4132 /**
4133 * Get the IME action label previous set with {@link #setImeActionLabel}.
4134 *
4135 * @see #setImeActionLabel
4136 * @see android.view.inputmethod.EditorInfo
4137 */
4138 public CharSequence getImeActionLabel() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004139 return mEditor != null && mEditor.mInputContentType != null
4140 ? mEditor.mInputContentType.imeActionLabel : null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004141 }
4142
4143 /**
4144 * Get the IME action ID previous set with {@link #setImeActionLabel}.
4145 *
4146 * @see #setImeActionLabel
4147 * @see android.view.inputmethod.EditorInfo
4148 */
4149 public int getImeActionId() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004150 return mEditor != null && mEditor.mInputContentType != null
4151 ? mEditor.mInputContentType.imeActionId : 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004152 }
4153
4154 /**
4155 * Set a special listener to be called when an action is performed
4156 * on the text view. This will be called when the enter key is pressed,
4157 * or when an action supplied to the IME is selected by the user. Setting
4158 * this means that the normal hard key event will not insert a newline
4159 * into the text view, even if it is multi-line; holding down the ALT
4160 * modifier will, however, allow the user to insert a newline character.
4161 */
4162 public void setOnEditorActionListener(OnEditorActionListener l) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004163 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004164 mEditor.createInputContentTypeIfNeeded();
4165 mEditor.mInputContentType.onEditorActionListener = l;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004166 }
Gilles Debunne60e21862012-01-30 15:04:14 -08004167
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004168 /**
4169 * Called when an attached input method calls
4170 * {@link InputConnection#performEditorAction(int)
4171 * InputConnection.performEditorAction()}
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07004172 * for this text view. The default implementation will call your action
4173 * listener supplied to {@link #setOnEditorActionListener}, or perform
4174 * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
Dianne Hackborndea3ef72010-10-28 14:24:22 -07004175 * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS
4176 * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07004177 * EditorInfo.IME_ACTION_DONE}.
Gilles Debunne2d373a12012-04-20 15:32:19 -07004178 *
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07004179 * <p>For backwards compatibility, if no IME options have been set and the
4180 * text view would not normally advance focus on enter, then
4181 * the NEXT and DONE actions received here will be turned into an enter
4182 * key down/up pair to go through the normal key handling.
Gilles Debunne2d373a12012-04-20 15:32:19 -07004183 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004184 * @param actionCode The code of the action being performed.
Gilles Debunne2d373a12012-04-20 15:32:19 -07004185 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004186 * @see #setOnEditorActionListener
4187 */
4188 public void onEditorAction(int actionCode) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004189 final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004190 if (ict != null) {
4191 if (ict.onEditorActionListener != null) {
4192 if (ict.onEditorActionListener.onEditorAction(this,
4193 actionCode, null)) {
4194 return;
4195 }
4196 }
Gilles Debunne64794482011-11-30 15:45:28 -08004197
The Android Open Source Project4df24232009-03-05 14:34:35 -08004198 // This is the handling for some default action.
4199 // Note that for backwards compatibility we don't do this
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004200 // default handling if explicit ime options have not been given,
The Android Open Source Project10592532009-03-18 17:39:46 -07004201 // instead turning this into the normal enter key codes that an
The Android Open Source Project4df24232009-03-05 14:34:35 -08004202 // app may be expecting.
4203 if (actionCode == EditorInfo.IME_ACTION_NEXT) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004204 View v = focusSearch(FOCUS_FORWARD);
The Android Open Source Project4df24232009-03-05 14:34:35 -08004205 if (v != null) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004206 if (!v.requestFocus(FOCUS_FORWARD)) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08004207 throw new IllegalStateException("focus search returned a view " +
4208 "that wasn't able to take focus!");
4209 }
4210 }
4211 return;
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004212
Dianne Hackborndea3ef72010-10-28 14:24:22 -07004213 } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004214 View v = focusSearch(FOCUS_BACKWARD);
Dianne Hackborndea3ef72010-10-28 14:24:22 -07004215 if (v != null) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004216 if (!v.requestFocus(FOCUS_BACKWARD)) {
Dianne Hackborndea3ef72010-10-28 14:24:22 -07004217 throw new IllegalStateException("focus search returned a view " +
4218 "that wasn't able to take focus!");
4219 }
4220 }
4221 return;
4222
The Android Open Source Project4df24232009-03-05 14:34:35 -08004223 } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
4224 InputMethodManager imm = InputMethodManager.peekInstance();
Gilles Debunne17d31de2011-01-27 11:02:18 -08004225 if (imm != null && imm.isActive(this)) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08004226 imm.hideSoftInputFromWindow(getWindowToken(), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004227 }
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07004228 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004229 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004230 }
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004231
Jeff Browna175a5b2012-02-15 19:18:31 -08004232 ViewRootImpl viewRootImpl = getViewRootImpl();
4233 if (viewRootImpl != null) {
The Android Open Source Project10592532009-03-18 17:39:46 -07004234 long eventTime = SystemClock.uptimeMillis();
Jeff Browna175a5b2012-02-15 19:18:31 -08004235 viewRootImpl.dispatchKeyFromIme(
The Android Open Source Project10592532009-03-18 17:39:46 -07004236 new KeyEvent(eventTime, eventTime,
Jeff Brown6b53e8d2010-11-10 16:03:06 -08004237 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
4238 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
The Android Open Source Project10592532009-03-18 17:39:46 -07004239 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
Jeff Browna175a5b2012-02-15 19:18:31 -08004240 | KeyEvent.FLAG_EDITOR_ACTION));
4241 viewRootImpl.dispatchKeyFromIme(
The Android Open Source Project10592532009-03-18 17:39:46 -07004242 new KeyEvent(SystemClock.uptimeMillis(), eventTime,
Jeff Brown6b53e8d2010-11-10 16:03:06 -08004243 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
4244 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
The Android Open Source Project10592532009-03-18 17:39:46 -07004245 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
Jeff Browna175a5b2012-02-15 19:18:31 -08004246 | KeyEvent.FLAG_EDITOR_ACTION));
The Android Open Source Project10592532009-03-18 17:39:46 -07004247 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004248 }
Gilles Debunne64794482011-11-30 15:45:28 -08004249
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004250 /**
4251 * Set the private content type of the text, which is the
4252 * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
4253 * field that will be filled in when creating an input connection.
4254 *
4255 * @see #getPrivateImeOptions()
4256 * @see EditorInfo#privateImeOptions
4257 * @attr ref android.R.styleable#TextView_privateImeOptions
4258 */
4259 public void setPrivateImeOptions(String type) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004260 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004261 mEditor.createInputContentTypeIfNeeded();
4262 mEditor.mInputContentType.privateImeOptions = type;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004263 }
4264
4265 /**
4266 * Get the private type of the content.
4267 *
4268 * @see #setPrivateImeOptions(String)
4269 * @see EditorInfo#privateImeOptions
4270 */
4271 public String getPrivateImeOptions() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004272 return mEditor != null && mEditor.mInputContentType != null
4273 ? mEditor.mInputContentType.privateImeOptions : null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004274 }
4275
4276 /**
4277 * Set the extra input data of the text, which is the
4278 * {@link EditorInfo#extras TextBoxAttribute.extras}
4279 * Bundle that will be filled in when creating an input connection. The
4280 * given integer is the resource ID of an XML resource holding an
4281 * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
4282 *
Gilles Debunne2d373a12012-04-20 15:32:19 -07004283 * @see #getInputExtras(boolean)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004284 * @see EditorInfo#extras
4285 * @attr ref android.R.styleable#TextView_editorExtras
4286 */
Gilles Debunne60e21862012-01-30 15:04:14 -08004287 public void setInputExtras(int xmlResId) throws XmlPullParserException, IOException {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004288 createEditorIfNeeded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004289 XmlResourceParser parser = getResources().getXml(xmlResId);
Gilles Debunne2d373a12012-04-20 15:32:19 -07004290 mEditor.createInputContentTypeIfNeeded();
4291 mEditor.mInputContentType.extras = new Bundle();
4292 getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004293 }
4294
4295 /**
4296 * Retrieve the input extras currently associated with the text view, which
4297 * can be viewed as well as modified.
4298 *
4299 * @param create If true, the extras will be created if they don't already
4300 * exist. Otherwise, null will be returned if none have been created.
Gilles Debunnee15b3582010-06-16 15:17:21 -07004301 * @see #setInputExtras(int)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004302 * @see EditorInfo#extras
4303 * @attr ref android.R.styleable#TextView_editorExtras
4304 */
4305 public Bundle getInputExtras(boolean create) {
Gilles Debunne60e21862012-01-30 15:04:14 -08004306 if (mEditor == null && !create) return null;
Gilles Debunne5fae9962012-05-08 14:53:20 -07004307 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004308 if (mEditor.mInputContentType == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004309 if (!create) return null;
Gilles Debunne2d373a12012-04-20 15:32:19 -07004310 mEditor.createInputContentTypeIfNeeded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004311 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07004312 if (mEditor.mInputContentType.extras == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004313 if (!create) return null;
Gilles Debunne2d373a12012-04-20 15:32:19 -07004314 mEditor.mInputContentType.extras = new Bundle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004315 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07004316 return mEditor.mInputContentType.extras;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004317 }
4318
4319 /**
4320 * Returns the error message that was set to be displayed with
4321 * {@link #setError}, or <code>null</code> if no error was set
4322 * or if it the error was cleared by the widget after user input.
4323 */
4324 public CharSequence getError() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004325 return mEditor == null ? null : mEditor.mError;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004326 }
4327
4328 /**
4329 * Sets the right-hand compound drawable of the TextView to the "error"
4330 * icon and sets an error message that will be displayed in a popup when
4331 * the TextView has focus. The icon and error message will be reset to
4332 * null when any key events cause changes to the TextView's text. If the
4333 * <code>error</code> is <code>null</code>, the error message and icon
4334 * will be cleared.
4335 */
4336 @android.view.RemotableViewMethod
4337 public void setError(CharSequence error) {
4338 if (error == null) {
4339 setError(null, null);
4340 } else {
4341 Drawable dr = getContext().getResources().
Gilles Debunnea85467b2011-01-19 16:53:31 -08004342 getDrawable(com.android.internal.R.drawable.indicator_input_error);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004343
4344 dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
4345 setError(error, dr);
4346 }
4347 }
4348
4349 /**
4350 * Sets the right-hand compound drawable of the TextView to the specified
4351 * icon and sets an error message that will be displayed in a popup when
4352 * the TextView has focus. The icon and error message will be reset to
4353 * null when any key events cause changes to the TextView's text. The
4354 * drawable must already have had {@link Drawable#setBounds} set on it.
4355 * If the <code>error</code> is <code>null</code>, the error message will
4356 * be cleared (and you should provide a <code>null</code> icon as well).
4357 */
4358 public void setError(CharSequence error, Drawable icon) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004359 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004360 mEditor.setError(error, icon);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004361 }
4362
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004363 @Override
4364 protected boolean setFrame(int l, int t, int r, int b) {
4365 boolean result = super.setFrame(l, t, r, b);
4366
Gilles Debunne2d373a12012-04-20 15:32:19 -07004367 if (mEditor != null) mEditor.setFrame();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004368
Romain Guy986003d2009-03-25 17:42:35 -07004369 restartMarqueeIfNeeded();
4370
4371 return result;
4372 }
4373
4374 private void restartMarqueeIfNeeded() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004375 if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
4376 mRestartMarquee = false;
4377 startMarquee();
4378 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004379 }
4380
4381 /**
4382 * Sets the list of input filters that will be used if the buffer is
Gilles Debunne60e21862012-01-30 15:04:14 -08004383 * Editable. Has no effect otherwise.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004384 *
4385 * @attr ref android.R.styleable#TextView_maxLength
4386 */
4387 public void setFilters(InputFilter[] filters) {
4388 if (filters == null) {
4389 throw new IllegalArgumentException();
4390 }
4391
4392 mFilters = filters;
4393
4394 if (mText instanceof Editable) {
4395 setFilters((Editable) mText, filters);
4396 }
4397 }
4398
4399 /**
4400 * Sets the list of input filters on the specified Editable,
4401 * and includes mInput in the list if it is an InputFilter.
4402 */
4403 private void setFilters(Editable e, InputFilter[] filters) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004404 if (mEditor != null && mEditor.mKeyListener instanceof InputFilter) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004405 InputFilter[] nf = new InputFilter[filters.length + 1];
4406
4407 System.arraycopy(filters, 0, nf, 0, filters.length);
Gilles Debunne2d373a12012-04-20 15:32:19 -07004408 nf[filters.length] = (InputFilter) mEditor.mKeyListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004409
4410 e.setFilters(nf);
4411 } else {
4412 e.setFilters(filters);
4413 }
4414 }
4415
4416 /**
4417 * Returns the current list of input filters.
Gilles Debunnef03acef2012-04-30 19:26:19 -07004418 *
4419 * @attr ref android.R.styleable#TextView_maxLength
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004420 */
4421 public InputFilter[] getFilters() {
4422 return mFilters;
4423 }
4424
4425 /////////////////////////////////////////////////////////////////////////
4426
Philip Milne7b757812012-09-19 18:13:44 -07004427 private int getBoxHeight(Layout l) {
4428 Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE;
4429 int padding = (l == mHintLayout) ?
4430 getCompoundPaddingTop() + getCompoundPaddingBottom() :
4431 getExtendedPaddingTop() + getExtendedPaddingBottom();
4432 return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom;
4433 }
4434
Gilles Debunned88876a2012-03-16 17:34:04 -07004435 int getVerticalOffset(boolean forceNormal) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004436 int voffset = 0;
4437 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
4438
4439 Layout l = mLayout;
4440 if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
4441 l = mHintLayout;
4442 }
4443
4444 if (gravity != Gravity.TOP) {
Philip Milne7b757812012-09-19 18:13:44 -07004445 int boxht = getBoxHeight(l);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004446 int textht = l.getHeight();
4447
4448 if (textht < boxht) {
4449 if (gravity == Gravity.BOTTOM)
4450 voffset = boxht - textht;
4451 else // (gravity == Gravity.CENTER_VERTICAL)
4452 voffset = (boxht - textht) >> 1;
4453 }
4454 }
4455 return voffset;
4456 }
4457
4458 private int getBottomVerticalOffset(boolean forceNormal) {
4459 int voffset = 0;
4460 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
4461
4462 Layout l = mLayout;
4463 if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
4464 l = mHintLayout;
4465 }
4466
4467 if (gravity != Gravity.BOTTOM) {
Philip Milne7b757812012-09-19 18:13:44 -07004468 int boxht = getBoxHeight(l);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004469 int textht = l.getHeight();
4470
4471 if (textht < boxht) {
4472 if (gravity == Gravity.TOP)
4473 voffset = boxht - textht;
4474 else // (gravity == Gravity.CENTER_VERTICAL)
4475 voffset = (boxht - textht) >> 1;
4476 }
4477 }
4478 return voffset;
4479 }
4480
Gilles Debunned88876a2012-03-16 17:34:04 -07004481 void invalidateCursorPath() {
Gilles Debunne83051b82012-02-24 20:01:13 -08004482 if (mHighlightPathBogus) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004483 invalidateCursor();
4484 } else {
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004485 final int horizontalPadding = getCompoundPaddingLeft();
4486 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004487
Gilles Debunne2d373a12012-04-20 15:32:19 -07004488 if (mEditor.mCursorCount == 0) {
Gilles Debunne60e21862012-01-30 15:04:14 -08004489 synchronized (TEMP_RECTF) {
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004490 /*
4491 * The reason for this concern about the thickness of the
4492 * cursor and doing the floor/ceil on the coordinates is that
4493 * some EditTexts (notably textfields in the Browser) have
4494 * anti-aliased text where not all the characters are
4495 * necessarily at integer-multiple locations. This should
4496 * make sure the entire cursor gets invalidated instead of
4497 * sometimes missing half a pixel.
4498 */
4499 float thick = FloatMath.ceil(mTextPaint.getStrokeWidth());
4500 if (thick < 1.0f) {
4501 thick = 1.0f;
4502 }
4503
4504 thick /= 2.0f;
4505
Gilles Debunne83051b82012-02-24 20:01:13 -08004506 // mHighlightPath is guaranteed to be non null at that point.
4507 mHighlightPath.computeBounds(TEMP_RECTF, false);
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004508
Gilles Debunne60e21862012-01-30 15:04:14 -08004509 invalidate((int) FloatMath.floor(horizontalPadding + TEMP_RECTF.left - thick),
4510 (int) FloatMath.floor(verticalPadding + TEMP_RECTF.top - thick),
4511 (int) FloatMath.ceil(horizontalPadding + TEMP_RECTF.right + thick),
4512 (int) FloatMath.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004513 }
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004514 } else {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004515 for (int i = 0; i < mEditor.mCursorCount; i++) {
4516 Rect bounds = mEditor.mCursorDrawable[i].getBounds();
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004517 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
4518 bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
4519 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004520 }
4521 }
4522 }
4523
Gilles Debunned88876a2012-03-16 17:34:04 -07004524 void invalidateCursor() {
Gilles Debunne05336272010-07-09 20:13:45 -07004525 int where = getSelectionEnd();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004526
4527 invalidateCursor(where, where, where);
4528 }
4529
4530 private void invalidateCursor(int a, int b, int c) {
Gilles Debunne8615ac92011-11-29 15:25:03 -08004531 if (a >= 0 || b >= 0 || c >= 0) {
4532 int start = Math.min(Math.min(a, b), c);
4533 int end = Math.max(Math.max(a, b), c);
Gilles Debunne961ebb92011-12-12 10:16:04 -08004534 invalidateRegion(start, end, true /* Also invalidates blinking cursor */);
Gilles Debunne8615ac92011-11-29 15:25:03 -08004535 }
4536 }
4537
4538 /**
4539 * Invalidates the region of text enclosed between the start and end text offsets.
Gilles Debunne8615ac92011-11-29 15:25:03 -08004540 */
Gilles Debunne961ebb92011-12-12 10:16:04 -08004541 void invalidateRegion(int start, int end, boolean invalidateCursor) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004542 if (mLayout == null) {
4543 invalidate();
4544 } else {
Gilles Debunne8615ac92011-11-29 15:25:03 -08004545 int lineStart = mLayout.getLineForOffset(start);
4546 int top = mLayout.getLineTop(lineStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004547
4548 // This is ridiculous, but the descent from the line above
4549 // can hang down into the line we really want to redraw,
4550 // so we have to invalidate part of the line above to make
4551 // sure everything that needs to be redrawn really is.
4552 // (But not the whole line above, because that would cause
4553 // the same problem with the descenders on the line above it!)
Gilles Debunne8615ac92011-11-29 15:25:03 -08004554 if (lineStart > 0) {
4555 top -= mLayout.getLineDescent(lineStart - 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004556 }
4557
Gilles Debunne8615ac92011-11-29 15:25:03 -08004558 int lineEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004559
Gilles Debunne8615ac92011-11-29 15:25:03 -08004560 if (start == end)
4561 lineEnd = lineStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004562 else
Gilles Debunne8615ac92011-11-29 15:25:03 -08004563 lineEnd = mLayout.getLineForOffset(end);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004564
Gilles Debunne8615ac92011-11-29 15:25:03 -08004565 int bottom = mLayout.getLineBottom(lineEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004566
Gilles Debunne83051b82012-02-24 20:01:13 -08004567 // mEditor can be null in case selection is set programmatically.
4568 if (invalidateCursor && mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004569 for (int i = 0; i < mEditor.mCursorCount; i++) {
4570 Rect bounds = mEditor.mCursorDrawable[i].getBounds();
Gilles Debunne961ebb92011-12-12 10:16:04 -08004571 top = Math.min(top, bounds.top);
4572 bottom = Math.max(bottom, bounds.bottom);
4573 }
4574 }
4575
Gilles Debunne8615ac92011-11-29 15:25:03 -08004576 final int compoundPaddingLeft = getCompoundPaddingLeft();
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004577 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
Gilles Debunne8615ac92011-11-29 15:25:03 -08004578
4579 int left, right;
Gilles Debunne961ebb92011-12-12 10:16:04 -08004580 if (lineStart == lineEnd && !invalidateCursor) {
Gilles Debunne8615ac92011-11-29 15:25:03 -08004581 left = (int) mLayout.getPrimaryHorizontal(start);
4582 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0);
4583 left += compoundPaddingLeft;
4584 right += compoundPaddingLeft;
4585 } else {
4586 // Rectangle bounding box when the region spans several lines
4587 left = compoundPaddingLeft;
4588 right = getWidth() - getCompoundPaddingRight();
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004589 }
4590
Gilles Debunne8615ac92011-11-29 15:25:03 -08004591 invalidate(mScrollX + left, verticalPadding + top,
4592 mScrollX + right, verticalPadding + bottom);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004593 }
4594 }
4595
4596 private void registerForPreDraw() {
Gilles Debunne2e37d622012-01-27 13:54:00 -08004597 if (!mPreDrawRegistered) {
4598 getViewTreeObserver().addOnPreDrawListener(this);
4599 mPreDrawRegistered = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004600 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004601 }
4602
4603 /**
4604 * {@inheritDoc}
4605 */
4606 public boolean onPreDraw() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004607 if (mLayout == null) {
4608 assumeLayout();
4609 }
4610
4611 boolean changed = false;
4612
4613 if (mMovement != null) {
Gilles Debunne05336272010-07-09 20:13:45 -07004614 /* This code also provides auto-scrolling when a cursor is moved using a
4615 * CursorController (insertion point or selection limits).
4616 * For selection, ensure start or end is visible depending on controller's state.
4617 */
4618 int curs = getSelectionEnd();
Gilles Debunnee587d832010-11-23 20:20:11 -08004619 // Do not create the controller if it is not already created.
Gilles Debunne2d373a12012-04-20 15:32:19 -07004620 if (mEditor != null && mEditor.mSelectionModifierCursorController != null &&
4621 mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07004622 curs = getSelectionStart();
Gilles Debunne05336272010-07-09 20:13:45 -07004623 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004624
4625 /*
4626 * TODO: This should really only keep the end in view if
4627 * it already was before the text changed. I'm not sure
4628 * of a good way to tell from here if it was.
4629 */
Gilles Debunne60e21862012-01-30 15:04:14 -08004630 if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004631 curs = mText.length();
4632 }
4633
4634 if (curs >= 0) {
4635 changed = bringPointIntoView(curs);
4636 }
4637 } else {
4638 changed = bringTextIntoView();
4639 }
4640
Gilles Debunne64e54a62010-09-07 19:07:17 -07004641 // This has to be checked here since:
4642 // - onFocusChanged cannot start it when focus is given to a view with selected text (after
4643 // a screen rotation) since layout is not yet initialized at that point.
Gilles Debunne2d373a12012-04-20 15:32:19 -07004644 if (mEditor != null && mEditor.mCreatedWithASelection) {
4645 mEditor.startSelectionActionMode();
4646 mEditor.mCreatedWithASelection = false;
Gilles Debunnec01f3fe2010-12-22 17:07:36 -08004647 }
4648
4649 // Phone specific code (there is no ExtractEditText on tablets).
4650 // ExtractEditText does not call onFocus when it is displayed, and mHasSelectionOnFocus can
4651 // not be set. Do the test here instead.
Gilles Debunned88876a2012-03-16 17:34:04 -07004652 if (this instanceof ExtractEditText && hasSelection() && mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004653 mEditor.startSelectionActionMode();
Gilles Debunnef788a9f2010-07-22 10:17:23 -07004654 }
Gilles Debunne64e54a62010-09-07 19:07:17 -07004655
Gilles Debunne2e37d622012-01-27 13:54:00 -08004656 getViewTreeObserver().removeOnPreDrawListener(this);
4657 mPreDrawRegistered = false;
4658
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004659 return !changed;
4660 }
4661
4662 @Override
4663 protected void onAttachedToWindow() {
4664 super.onAttachedToWindow();
4665
4666 mTemporaryDetach = false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07004667
Gilles Debunne2d373a12012-04-20 15:32:19 -07004668 if (mEditor != null) mEditor.onAttachedToWindow();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004669 }
4670
4671 @Override
4672 protected void onDetachedFromWindow() {
4673 super.onDetachedFromWindow();
4674
Gilles Debunne2e37d622012-01-27 13:54:00 -08004675 if (mPreDrawRegistered) {
4676 getViewTreeObserver().removeOnPreDrawListener(this);
4677 mPreDrawRegistered = false;
Gilles Debunne81f08082011-02-17 14:07:19 -08004678 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004679
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07004680 resetResolvedDrawables();
Gilles Debunne186aaf92011-09-16 14:26:12 -07004681
Gilles Debunne2d373a12012-04-20 15:32:19 -07004682 if (mEditor != null) mEditor.onDetachedFromWindow();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004683 }
4684
4685 @Override
Romain Guybb9908b2012-03-08 11:14:07 -08004686 public void onScreenStateChanged(int screenState) {
4687 super.onScreenStateChanged(screenState);
Gilles Debunne2d373a12012-04-20 15:32:19 -07004688 if (mEditor != null) mEditor.onScreenStateChanged(screenState);
Romain Guybb9908b2012-03-08 11:14:07 -08004689 }
4690
4691 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004692 protected boolean isPaddingOffsetRequired() {
Romain Guy076dc9f2009-06-24 17:17:51 -07004693 return mShadowRadius != 0 || mDrawables != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004694 }
4695
4696 @Override
4697 protected int getLeftPaddingOffset() {
Romain Guy076dc9f2009-06-24 17:17:51 -07004698 return getCompoundPaddingLeft() - mPaddingLeft +
4699 (int) Math.min(0, mShadowDx - mShadowRadius);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004700 }
4701
4702 @Override
4703 protected int getTopPaddingOffset() {
4704 return (int) Math.min(0, mShadowDy - mShadowRadius);
4705 }
4706
4707 @Override
4708 protected int getBottomPaddingOffset() {
4709 return (int) Math.max(0, mShadowDy + mShadowRadius);
4710 }
4711
4712 @Override
4713 protected int getRightPaddingOffset() {
Romain Guy076dc9f2009-06-24 17:17:51 -07004714 return -(getCompoundPaddingRight() - mPaddingRight) +
4715 (int) Math.max(0, mShadowDx + mShadowRadius);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004716 }
4717
4718 @Override
4719 protected boolean verifyDrawable(Drawable who) {
4720 final boolean verified = super.verifyDrawable(who);
4721 if (!verified && mDrawables != null) {
4722 return who == mDrawables.mDrawableLeft || who == mDrawables.mDrawableTop ||
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07004723 who == mDrawables.mDrawableRight || who == mDrawables.mDrawableBottom ||
4724 who == mDrawables.mDrawableStart || who == mDrawables.mDrawableEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004725 }
4726 return verified;
4727 }
4728
4729 @Override
Dianne Hackborne2136772010-11-04 15:08:59 -07004730 public void jumpDrawablesToCurrentState() {
4731 super.jumpDrawablesToCurrentState();
4732 if (mDrawables != null) {
4733 if (mDrawables.mDrawableLeft != null) {
4734 mDrawables.mDrawableLeft.jumpToCurrentState();
4735 }
4736 if (mDrawables.mDrawableTop != null) {
4737 mDrawables.mDrawableTop.jumpToCurrentState();
4738 }
4739 if (mDrawables.mDrawableRight != null) {
4740 mDrawables.mDrawableRight.jumpToCurrentState();
4741 }
4742 if (mDrawables.mDrawableBottom != null) {
4743 mDrawables.mDrawableBottom.jumpToCurrentState();
4744 }
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07004745 if (mDrawables.mDrawableStart != null) {
4746 mDrawables.mDrawableStart.jumpToCurrentState();
4747 }
4748 if (mDrawables.mDrawableEnd != null) {
4749 mDrawables.mDrawableEnd.jumpToCurrentState();
4750 }
Dianne Hackborne2136772010-11-04 15:08:59 -07004751 }
4752 }
4753
4754 @Override
Romain Guy3c77d392009-05-20 11:26:50 -07004755 public void invalidateDrawable(Drawable drawable) {
4756 if (verifyDrawable(drawable)) {
4757 final Rect dirty = drawable.getBounds();
4758 int scrollX = mScrollX;
4759 int scrollY = mScrollY;
4760
4761 // IMPORTANT: The coordinates below are based on the coordinates computed
4762 // for each compound drawable in onDraw(). Make sure to update each section
4763 // accordingly.
4764 final TextView.Drawables drawables = mDrawables;
Romain Guya6cd4e02009-05-20 15:09:21 -07004765 if (drawables != null) {
4766 if (drawable == drawables.mDrawableLeft) {
4767 final int compoundPaddingTop = getCompoundPaddingTop();
4768 final int compoundPaddingBottom = getCompoundPaddingBottom();
4769 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
Romain Guy3c77d392009-05-20 11:26:50 -07004770
Romain Guya6cd4e02009-05-20 15:09:21 -07004771 scrollX += mPaddingLeft;
4772 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
4773 } else if (drawable == drawables.mDrawableRight) {
4774 final int compoundPaddingTop = getCompoundPaddingTop();
4775 final int compoundPaddingBottom = getCompoundPaddingBottom();
4776 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
Romain Guy3c77d392009-05-20 11:26:50 -07004777
Romain Guya6cd4e02009-05-20 15:09:21 -07004778 scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
4779 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
4780 } else if (drawable == drawables.mDrawableTop) {
4781 final int compoundPaddingLeft = getCompoundPaddingLeft();
4782 final int compoundPaddingRight = getCompoundPaddingRight();
4783 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
Romain Guy3c77d392009-05-20 11:26:50 -07004784
Romain Guya6cd4e02009-05-20 15:09:21 -07004785 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
4786 scrollY += mPaddingTop;
4787 } else if (drawable == drawables.mDrawableBottom) {
4788 final int compoundPaddingLeft = getCompoundPaddingLeft();
4789 final int compoundPaddingRight = getCompoundPaddingRight();
4790 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
Romain Guy3c77d392009-05-20 11:26:50 -07004791
Romain Guya6cd4e02009-05-20 15:09:21 -07004792 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
4793 scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
4794 }
Romain Guy3c77d392009-05-20 11:26:50 -07004795 }
4796
4797 invalidate(dirty.left + scrollX, dirty.top + scrollY,
4798 dirty.right + scrollX, dirty.bottom + scrollY);
4799 }
4800 }
4801
4802 @Override
Chet Haasedb8c9a62012-03-21 18:54:18 -07004803 public boolean hasOverlappingRendering() {
Michael Jurka0931a852013-03-21 16:07:45 +01004804 return ((getBackground() != null && getBackground().getCurrent() != null)
4805 || mText instanceof Spannable || hasSelection());
Chet Haasedb8c9a62012-03-21 18:54:18 -07004806 }
4807
Gilles Debunne86b9c782010-11-11 10:43:48 -08004808 /**
Gilles Debunne86b9c782010-11-11 10:43:48 -08004809 *
Joe Malin10d96952013-05-29 17:49:09 -07004810 * Returns the state of the {@code textIsSelectable} flag (See
4811 * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag
4812 * to allow users to select and copy text in a non-editable TextView, the content of an
4813 * {@link EditText} can always be selected, independently of the value of this flag.
4814 * <p>
Gilles Debunne86b9c782010-11-11 10:43:48 -08004815 *
4816 * @return True if the text displayed in this TextView can be selected by the user.
4817 *
4818 * @attr ref android.R.styleable#TextView_textIsSelectable
4819 */
4820 public boolean isTextSelectable() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004821 return mEditor == null ? false : mEditor.mTextIsSelectable;
Gilles Debunne86b9c782010-11-11 10:43:48 -08004822 }
4823
4824 /**
Joe Malin10d96952013-05-29 17:49:09 -07004825 * Sets whether the content of this view is selectable by the user. The default is
4826 * {@code false}, meaning that the content is not selectable.
4827 * <p>
4828 * When you use a TextView to display a useful piece of information to the user (such as a
4829 * contact's address), make it selectable, so that the user can select and copy its
4830 * content. You can also use set the XML attribute
4831 * {@link android.R.styleable#TextView_textIsSelectable} to "true".
4832 * <p>
4833 * When you call this method to set the value of {@code textIsSelectable}, it sets
4834 * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable},
4835 * and {@code longClickable} to the same value. These flags correspond to the attributes
4836 * {@link android.R.styleable#View_focusable android:focusable},
4837 * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode},
4838 * {@link android.R.styleable#View_clickable android:clickable}, and
4839 * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these
4840 * flags to a state you had set previously, call one or more of the following methods:
4841 * {@link #setFocusable(boolean) setFocusable()},
4842 * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()},
4843 * {@link #setClickable(boolean) setClickable()} or
4844 * {@link #setLongClickable(boolean) setLongClickable()}.
Gilles Debunne60e21862012-01-30 15:04:14 -08004845 *
Joe Malin10d96952013-05-29 17:49:09 -07004846 * @param selectable Whether the content of this TextView should be selectable.
Gilles Debunne86b9c782010-11-11 10:43:48 -08004847 */
4848 public void setTextIsSelectable(boolean selectable) {
Gilles Debunne60e21862012-01-30 15:04:14 -08004849 if (!selectable && mEditor == null) return; // false is default value with no edit data
Gilles Debunne86b9c782010-11-11 10:43:48 -08004850
Gilles Debunne5fae9962012-05-08 14:53:20 -07004851 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004852 if (mEditor.mTextIsSelectable == selectable) return;
Gilles Debunne86b9c782010-11-11 10:43:48 -08004853
Gilles Debunne2d373a12012-04-20 15:32:19 -07004854 mEditor.mTextIsSelectable = selectable;
Gilles Debunnecbcb3452010-12-17 15:31:02 -08004855 setFocusableInTouchMode(selectable);
Gilles Debunne86b9c782010-11-11 10:43:48 -08004856 setFocusable(selectable);
4857 setClickable(selectable);
4858 setLongClickable(selectable);
4859
Gilles Debunne60e21862012-01-30 15:04:14 -08004860 // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
Gilles Debunne86b9c782010-11-11 10:43:48 -08004861
4862 setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
Gilles Debunne857c3412012-06-07 10:50:58 -07004863 setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
Gilles Debunne86b9c782010-11-11 10:43:48 -08004864
4865 // Called by setText above, but safer in case of future code changes
Gilles Debunne2d373a12012-04-20 15:32:19 -07004866 mEditor.prepareCursorControllers();
Gilles Debunne86b9c782010-11-11 10:43:48 -08004867 }
4868
4869 @Override
4870 protected int[] onCreateDrawableState(int extraSpace) {
Gilles Debunnefb817032011-01-13 13:52:49 -08004871 final int[] drawableState;
Gilles Debunneda0a3f02010-12-22 10:07:15 -08004872
Gilles Debunnefb817032011-01-13 13:52:49 -08004873 if (mSingleLine) {
4874 drawableState = super.onCreateDrawableState(extraSpace);
4875 } else {
4876 drawableState = super.onCreateDrawableState(extraSpace + 1);
Gilles Debunneda0a3f02010-12-22 10:07:15 -08004877 mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
4878 }
4879
Gilles Debunne60e21862012-01-30 15:04:14 -08004880 if (isTextSelectable()) {
Gilles Debunne86b9c782010-11-11 10:43:48 -08004881 // Disable pressed state, which was introduced when TextView was made clickable.
4882 // Prevents text color change.
4883 // setClickable(false) would have a similar effect, but it also disables focus changes
4884 // and long press actions, which are both needed by text selection.
4885 final int length = drawableState.length;
4886 for (int i = 0; i < length; i++) {
4887 if (drawableState[i] == R.attr.state_pressed) {
4888 final int[] nonPressedState = new int[length - 1];
4889 System.arraycopy(drawableState, 0, nonPressedState, 0, i);
4890 System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
4891 return nonPressedState;
4892 }
4893 }
4894 }
Gilles Debunneda0a3f02010-12-22 10:07:15 -08004895
Gilles Debunne86b9c782010-11-11 10:43:48 -08004896 return drawableState;
4897 }
4898
Gilles Debunne83051b82012-02-24 20:01:13 -08004899 private Path getUpdatedHighlightPath() {
4900 Path highlight = null;
4901 Paint highlightPaint = mHighlightPaint;
4902
4903 final int selStart = getSelectionStart();
4904 final int selEnd = getSelectionEnd();
4905 if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
4906 if (selStart == selEnd) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004907 if (mEditor != null && mEditor.isCursorVisible() &&
4908 (SystemClock.uptimeMillis() - mEditor.mShowCursor) %
Gilles Debunned88876a2012-03-16 17:34:04 -07004909 (2 * Editor.BLINK) < Editor.BLINK) {
Gilles Debunne83051b82012-02-24 20:01:13 -08004910 if (mHighlightPathBogus) {
4911 if (mHighlightPath == null) mHighlightPath = new Path();
4912 mHighlightPath.reset();
4913 mLayout.getCursorPath(selStart, mHighlightPath, mText);
Gilles Debunne2d373a12012-04-20 15:32:19 -07004914 mEditor.updateCursorsPositions();
Gilles Debunne83051b82012-02-24 20:01:13 -08004915 mHighlightPathBogus = false;
4916 }
4917
4918 // XXX should pass to skin instead of drawing directly
4919 highlightPaint.setColor(mCurTextColor);
Gilles Debunne83051b82012-02-24 20:01:13 -08004920 highlightPaint.setStyle(Paint.Style.STROKE);
4921 highlight = mHighlightPath;
4922 }
4923 } else {
4924 if (mHighlightPathBogus) {
4925 if (mHighlightPath == null) mHighlightPath = new Path();
4926 mHighlightPath.reset();
4927 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
4928 mHighlightPathBogus = false;
4929 }
4930
4931 // XXX should pass to skin instead of drawing directly
4932 highlightPaint.setColor(mHighlightColor);
Gilles Debunne83051b82012-02-24 20:01:13 -08004933 highlightPaint.setStyle(Paint.Style.FILL);
4934
4935 highlight = mHighlightPath;
4936 }
4937 }
4938 return highlight;
4939 }
4940
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -08004941 /**
4942 * @hide
4943 */
4944 public int getHorizontalOffsetForDrawables() {
4945 return 0;
4946 }
4947
Romain Guyc4d8eb62010-08-18 20:48:33 -07004948 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004949 protected void onDraw(Canvas canvas) {
Romain Guy986003d2009-03-25 17:42:35 -07004950 restartMarqueeIfNeeded();
4951
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004952 // Draw the background for this view
4953 super.onDraw(canvas);
4954
4955 final int compoundPaddingLeft = getCompoundPaddingLeft();
4956 final int compoundPaddingTop = getCompoundPaddingTop();
4957 final int compoundPaddingRight = getCompoundPaddingRight();
4958 final int compoundPaddingBottom = getCompoundPaddingBottom();
4959 final int scrollX = mScrollX;
4960 final int scrollY = mScrollY;
4961 final int right = mRight;
4962 final int left = mLeft;
4963 final int bottom = mBottom;
4964 final int top = mTop;
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -08004965 final boolean isLayoutRtl = isLayoutRtl();
4966 final int offset = getHorizontalOffsetForDrawables();
4967 final int leftOffset = isLayoutRtl ? 0 : offset;
4968 final int rightOffset = isLayoutRtl ? offset : 0 ;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004969
4970 final Drawables dr = mDrawables;
4971 if (dr != null) {
4972 /*
4973 * Compound, not extended, because the icon is not clipped
4974 * if the text height is smaller.
4975 */
4976
4977 int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
4978 int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
4979
Romain Guy3c77d392009-05-20 11:26:50 -07004980 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
4981 // Make sure to update invalidateDrawable() when changing this code.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004982 if (dr.mDrawableLeft != null) {
4983 canvas.save();
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -08004984 canvas.translate(scrollX + mPaddingLeft + leftOffset,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004985 scrollY + compoundPaddingTop +
4986 (vspace - dr.mDrawableHeightLeft) / 2);
4987 dr.mDrawableLeft.draw(canvas);
4988 canvas.restore();
4989 }
4990
Romain Guy3c77d392009-05-20 11:26:50 -07004991 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
4992 // Make sure to update invalidateDrawable() when changing this code.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004993 if (dr.mDrawableRight != null) {
4994 canvas.save();
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -08004995 canvas.translate(scrollX + right - left - mPaddingRight
4996 - dr.mDrawableSizeRight - rightOffset,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004997 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
4998 dr.mDrawableRight.draw(canvas);
4999 canvas.restore();
5000 }
5001
Romain Guy3c77d392009-05-20 11:26:50 -07005002 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5003 // Make sure to update invalidateDrawable() when changing this code.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005004 if (dr.mDrawableTop != null) {
5005 canvas.save();
Gilles Debunne2d373a12012-04-20 15:32:19 -07005006 canvas.translate(scrollX + compoundPaddingLeft +
5007 (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005008 dr.mDrawableTop.draw(canvas);
5009 canvas.restore();
5010 }
5011
Romain Guy3c77d392009-05-20 11:26:50 -07005012 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5013 // Make sure to update invalidateDrawable() when changing this code.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005014 if (dr.mDrawableBottom != null) {
5015 canvas.save();
5016 canvas.translate(scrollX + compoundPaddingLeft +
5017 (hspace - dr.mDrawableWidthBottom) / 2,
5018 scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
5019 dr.mDrawableBottom.draw(canvas);
5020 canvas.restore();
5021 }
5022 }
5023
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005024 int color = mCurTextColor;
5025
5026 if (mLayout == null) {
5027 assumeLayout();
5028 }
5029
5030 Layout layout = mLayout;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005031
5032 if (mHint != null && mText.length() == 0) {
5033 if (mHintTextColor != null) {
5034 color = mCurHintTextColor;
5035 }
5036
5037 layout = mHintLayout;
5038 }
5039
5040 mTextPaint.setColor(color);
5041 mTextPaint.drawableState = getDrawableState();
5042
5043 canvas.save();
5044 /* Would be faster if we didn't have to do this. Can we chop the
5045 (displayable) text so that we don't need to do this ever?
5046 */
5047
5048 int extendedPaddingTop = getExtendedPaddingTop();
5049 int extendedPaddingBottom = getExtendedPaddingBottom();
5050
Fabrice Di Meglio132bda12012-02-07 17:02:00 -08005051 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
5052 final int maxScrollY = mLayout.getHeight() - vspace;
5053
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005054 float clipLeft = compoundPaddingLeft + scrollX;
Fabrice Di Meglio132bda12012-02-07 17:02:00 -08005055 float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005056 float clipRight = right - left - compoundPaddingRight + scrollX;
Fabrice Di Meglio132bda12012-02-07 17:02:00 -08005057 float clipBottom = bottom - top + scrollY -
5058 ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005059
5060 if (mShadowRadius != 0) {
5061 clipLeft += Math.min(0, mShadowDx - mShadowRadius);
5062 clipRight += Math.max(0, mShadowDx + mShadowRadius);
5063
5064 clipTop += Math.min(0, mShadowDy - mShadowRadius);
5065 clipBottom += Math.max(0, mShadowDy + mShadowRadius);
5066 }
5067
5068 canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
5069
5070 int voffsetText = 0;
5071 int voffsetCursor = 0;
5072
5073 // translate in by our padding
Gilles Debunne60e21862012-01-30 15:04:14 -08005074 /* shortcircuit calling getVerticaOffset() */
5075 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5076 voffsetText = getVerticalOffset(false);
5077 voffsetCursor = getVerticalOffset(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005078 }
Gilles Debunne60e21862012-01-30 15:04:14 -08005079 canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005080
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07005081 final int layoutDirection = getLayoutDirection();
Fabrice Di Meglioc0053222011-06-13 12:16:51 -07005082 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
Adam Powell282e3772011-08-30 16:51:11 -07005083 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
5084 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005085 if (!mSingleLine && getLineCount() == 1 && canMarquee() &&
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07005086 (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07005087 final int width = mRight - mLeft;
5088 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
5089 final float dx = mLayout.getLineRight(0) - (width - padding);
5090 canvas.translate(isLayoutRtl ? -dx : +dx, 0.0f);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005091 }
5092
5093 if (mMarquee != null && mMarquee.isRunning()) {
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07005094 final float dx = -mMarquee.getScroll();
5095 canvas.translate(isLayoutRtl ? -dx : +dx, 0.0f);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005096 }
5097 }
5098
Gilles Debunne12d91ce2010-12-10 11:36:29 -08005099 final int cursorOffsetVertical = voffsetCursor - voffsetText;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005100
Gilles Debunne83051b82012-02-24 20:01:13 -08005101 Path highlight = getUpdatedHighlightPath();
Gilles Debunne60e21862012-01-30 15:04:14 -08005102 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005103 mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
Gilles Debunneb35ab7b2011-12-05 15:54:00 -08005104 } else {
Gilles Debunne83051b82012-02-24 20:01:13 -08005105 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
Gilles Debunned88876a2012-03-16 17:34:04 -07005106 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005107
Gilles Debunned88876a2012-03-16 17:34:04 -07005108 if (mMarquee != null && mMarquee.shouldDrawGhost()) {
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07005109 final int dx = (int) mMarquee.getGhostOffset();
5110 canvas.translate(isLayoutRtl ? -dx : dx, 0.0f);
Gilles Debunned88876a2012-03-16 17:34:04 -07005111 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
Romain Guyc2303192009-04-03 17:37:18 -07005112 }
5113
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005114 canvas.restore();
Leon Scroggins56426252010-11-01 15:45:37 -04005115 }
5116
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005117 @Override
5118 public void getFocusedRect(Rect r) {
5119 if (mLayout == null) {
5120 super.getFocusedRect(r);
5121 return;
5122 }
5123
Dianne Hackborn70a3f672011-08-08 14:32:41 -07005124 int selEnd = getSelectionEnd();
5125 if (selEnd < 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005126 super.getFocusedRect(r);
5127 return;
5128 }
5129
Dianne Hackborn70a3f672011-08-08 14:32:41 -07005130 int selStart = getSelectionStart();
5131 if (selStart < 0 || selStart >= selEnd) {
5132 int line = mLayout.getLineForOffset(selEnd);
5133 r.top = mLayout.getLineTop(line);
5134 r.bottom = mLayout.getLineBottom(line);
5135 r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
5136 r.right = r.left + 4;
5137 } else {
5138 int lineStart = mLayout.getLineForOffset(selStart);
5139 int lineEnd = mLayout.getLineForOffset(selEnd);
5140 r.top = mLayout.getLineTop(lineStart);
5141 r.bottom = mLayout.getLineBottom(lineEnd);
5142 if (lineStart == lineEnd) {
5143 r.left = (int) mLayout.getPrimaryHorizontal(selStart);
5144 r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
5145 } else {
Gilles Debunne60e21862012-01-30 15:04:14 -08005146 // Selection extends across multiple lines -- make the focused
5147 // rect cover the entire width.
Gilles Debunne83051b82012-02-24 20:01:13 -08005148 if (mHighlightPathBogus) {
5149 if (mHighlightPath == null) mHighlightPath = new Path();
5150 mHighlightPath.reset();
5151 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
5152 mHighlightPathBogus = false;
5153 }
5154 synchronized (TEMP_RECTF) {
5155 mHighlightPath.computeBounds(TEMP_RECTF, true);
5156 r.left = (int)TEMP_RECTF.left-1;
5157 r.right = (int)TEMP_RECTF.right+1;
Dianne Hackborn70a3f672011-08-08 14:32:41 -07005158 }
5159 }
5160 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005161
5162 // Adjust for padding and gravity.
5163 int paddingLeft = getCompoundPaddingLeft();
5164 int paddingTop = getExtendedPaddingTop();
5165 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5166 paddingTop += getVerticalOffset(false);
5167 }
5168 r.offset(paddingLeft, paddingTop);
Gilles Debunne322044a2012-02-22 12:01:40 -08005169 int paddingBottom = getExtendedPaddingBottom();
5170 r.bottom += paddingBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005171 }
5172
5173 /**
5174 * Return the number of lines of text, or 0 if the internal Layout has not
5175 * been built.
5176 */
5177 public int getLineCount() {
5178 return mLayout != null ? mLayout.getLineCount() : 0;
5179 }
5180
5181 /**
5182 * Return the baseline for the specified line (0...getLineCount() - 1)
5183 * If bounds is not null, return the top, left, right, bottom extents
5184 * of the specified line in it. If the internal Layout has not been built,
5185 * return 0 and set bounds to (0, 0, 0, 0)
5186 * @param line which line to examine (0..getLineCount() - 1)
5187 * @param bounds Optional. If not null, it returns the extent of the line
5188 * @return the Y-coordinate of the baseline
5189 */
5190 public int getLineBounds(int line, Rect bounds) {
5191 if (mLayout == null) {
5192 if (bounds != null) {
5193 bounds.set(0, 0, 0, 0);
5194 }
5195 return 0;
5196 }
5197 else {
5198 int baseline = mLayout.getLineBounds(line, bounds);
5199
5200 int voffset = getExtendedPaddingTop();
5201 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5202 voffset += getVerticalOffset(true);
5203 }
5204 if (bounds != null) {
5205 bounds.offset(getCompoundPaddingLeft(), voffset);
5206 }
5207 return baseline + voffset;
5208 }
5209 }
5210
5211 @Override
5212 public int getBaseline() {
5213 if (mLayout == null) {
5214 return super.getBaseline();
5215 }
5216
5217 int voffset = 0;
5218 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5219 voffset = getVerticalOffset(true);
5220 }
5221
Philip Milne7b757812012-09-19 18:13:44 -07005222 if (isLayoutModeOptical(mParent)) {
5223 voffset -= getOpticalInsets().top;
5224 }
5225
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005226 return getExtendedPaddingTop() + voffset + mLayout.getLineBaseline(0);
5227 }
5228
Romain Guyf2fc4602011-07-19 15:20:03 -07005229 /**
5230 * @hide
Romain Guyf2fc4602011-07-19 15:20:03 -07005231 */
5232 @Override
5233 protected int getFadeTop(boolean offsetRequired) {
Romain Guy59f13c7d2011-07-19 18:35:33 -07005234 if (mLayout == null) return 0;
5235
Romain Guyf2fc4602011-07-19 15:20:03 -07005236 int voffset = 0;
5237 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5238 voffset = getVerticalOffset(true);
5239 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005240
Romain Guyf2fc4602011-07-19 15:20:03 -07005241 if (offsetRequired) voffset += getTopPaddingOffset();
5242
5243 return getExtendedPaddingTop() + voffset;
5244 }
5245
5246 /**
5247 * @hide
Romain Guyf2fc4602011-07-19 15:20:03 -07005248 */
Gilles Debunne3784a7f2011-07-15 13:49:38 -07005249 @Override
Romain Guyf2fc4602011-07-19 15:20:03 -07005250 protected int getFadeHeight(boolean offsetRequired) {
5251 return mLayout != null ? mLayout.getHeight() : 0;
5252 }
5253
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005254 @Override
Gilles Debunne4a7199a2011-07-11 14:58:27 -07005255 public boolean onKeyPreIme(int keyCode, KeyEvent event) {
5256 if (keyCode == KeyEvent.KEYCODE_BACK) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005257 boolean isInSelectionMode = mEditor != null && mEditor.mSelectionActionMode != null;
Gilles Debunne4a7199a2011-07-11 14:58:27 -07005258
Gilles Debunne28294cc2011-08-24 12:02:05 -07005259 if (isInSelectionMode) {
Gilles Debunne4a7199a2011-07-11 14:58:27 -07005260 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
5261 KeyEvent.DispatcherState state = getKeyDispatcherState();
5262 if (state != null) {
5263 state.startTracking(event, this);
5264 }
5265 return true;
5266 } else if (event.getAction() == KeyEvent.ACTION_UP) {
5267 KeyEvent.DispatcherState state = getKeyDispatcherState();
5268 if (state != null) {
5269 state.handleUpEvent(event);
5270 }
5271 if (event.isTracking() && !event.isCanceled()) {
Gilles Debunne14568c32012-01-13 15:26:05 -08005272 stopSelectionActionMode();
5273 return true;
Gilles Debunne4a7199a2011-07-11 14:58:27 -07005274 }
5275 }
5276 }
5277 }
5278 return super.onKeyPreIme(keyCode, event);
5279 }
5280
5281 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005282 public boolean onKeyDown(int keyCode, KeyEvent event) {
5283 int which = doKeyDown(keyCode, event, null);
5284 if (which == 0) {
5285 // Go through default dispatching.
5286 return super.onKeyDown(keyCode, event);
5287 }
5288
5289 return true;
5290 }
5291
5292 @Override
5293 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005294 KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005295
5296 int which = doKeyDown(keyCode, down, event);
5297 if (which == 0) {
5298 // Go through default dispatching.
5299 return super.onKeyMultiple(keyCode, repeatCount, event);
5300 }
5301 if (which == -1) {
5302 // Consumed the whole thing.
5303 return true;
5304 }
5305
5306 repeatCount--;
Gilles Debunne2d373a12012-04-20 15:32:19 -07005307
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005308 // We are going to dispatch the remaining events to either the input
5309 // or movement method. To do this, we will just send a repeated stream
5310 // of down and up events until we have done the complete repeatCount.
5311 // It would be nice if those interfaces had an onKeyMultiple() method,
5312 // but adding that is a more complicated change.
The Android Open Source Project10592532009-03-18 17:39:46 -07005313 KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005314 if (which == 1) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005315 // mEditor and mEditor.mInput are not null from doKeyDown
5316 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005317 while (--repeatCount > 0) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005318 mEditor.mKeyListener.onKeyDown(this, (Editable)mText, keyCode, down);
5319 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005320 }
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08005321 hideErrorIfUnchanged();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005322
5323 } else if (which == 2) {
Gilles Debunne60e21862012-01-30 15:04:14 -08005324 // mMovement is not null from doKeyDown
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005325 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
5326 while (--repeatCount > 0) {
5327 mMovement.onKeyDown(this, (Spannable)mText, keyCode, down);
5328 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
5329 }
5330 }
5331
5332 return true;
5333 }
5334
5335 /**
5336 * Returns true if pressing ENTER in this field advances focus instead
5337 * of inserting the character. This is true mostly in single-line fields,
5338 * but also in mail addresses and subjects which will display on multiple
5339 * lines but where it doesn't make sense to insert newlines.
5340 */
The Android Open Source Project4df24232009-03-05 14:34:35 -08005341 private boolean shouldAdvanceFocusOnEnter() {
Gilles Debunne60e21862012-01-30 15:04:14 -08005342 if (getKeyListener() == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005343 return false;
5344 }
5345
5346 if (mSingleLine) {
5347 return true;
5348 }
5349
Gilles Debunne2d373a12012-04-20 15:32:19 -07005350 if (mEditor != null &&
5351 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
5352 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005353 if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
5354 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005355 return true;
5356 }
5357 }
5358
5359 return false;
5360 }
5361
Jeff Brown4e6319b2010-12-13 10:36:51 -08005362 /**
5363 * Returns true if pressing TAB in this field advances focus instead
5364 * of inserting the character. Insert tabs only in multi-line editors.
5365 */
5366 private boolean shouldAdvanceFocusOnTab() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005367 if (getKeyListener() != null && !mSingleLine && mEditor != null &&
5368 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
5369 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
5370 if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
5371 || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
5372 return false;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005373 }
5374 }
5375 return true;
5376 }
5377
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005378 private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
5379 if (!isEnabled()) {
5380 return 0;
5381 }
5382
5383 switch (keyCode) {
5384 case KeyEvent.KEYCODE_ENTER:
Jeff Brown4e6319b2010-12-13 10:36:51 -08005385 if (event.hasNoModifiers()) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005386 // When mInputContentType is set, we know that we are
5387 // running in a "modern" cupcake environment, so don't need
5388 // to worry about the application trying to capture
5389 // enter key events.
Gilles Debunne2d373a12012-04-20 15:32:19 -07005390 if (mEditor != null && mEditor.mInputContentType != null) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005391 // If there is an action listener, given them a
5392 // chance to consume the event.
Gilles Debunne2d373a12012-04-20 15:32:19 -07005393 if (mEditor.mInputContentType.onEditorActionListener != null &&
5394 mEditor.mInputContentType.onEditorActionListener.onEditorAction(
The Android Open Source Project10592532009-03-18 17:39:46 -07005395 this, EditorInfo.IME_NULL, event)) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005396 mEditor.mInputContentType.enterDown = true;
The Android Open Source Project10592532009-03-18 17:39:46 -07005397 // We are consuming the enter key for them.
5398 return -1;
5399 }
5400 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08005401
The Android Open Source Project10592532009-03-18 17:39:46 -07005402 // If our editor should move focus when enter is pressed, or
5403 // this is a generated event from an IME action button, then
5404 // don't let it be inserted into the text.
Jeff Brown4e6319b2010-12-13 10:36:51 -08005405 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
The Android Open Source Project10592532009-03-18 17:39:46 -07005406 || shouldAdvanceFocusOnEnter()) {
Dianne Hackborn0500b3c2011-11-01 15:28:43 -07005407 if (hasOnClickListeners()) {
Leon Scroggins7014b122011-01-11 15:17:34 -05005408 return 0;
5409 }
The Android Open Source Project10592532009-03-18 17:39:46 -07005410 return -1;
5411 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005412 }
The Android Open Source Project10592532009-03-18 17:39:46 -07005413 break;
Gilles Debunne2d373a12012-04-20 15:32:19 -07005414
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005415 case KeyEvent.KEYCODE_DPAD_CENTER:
Jeff Brown4e6319b2010-12-13 10:36:51 -08005416 if (event.hasNoModifiers()) {
5417 if (shouldAdvanceFocusOnEnter()) {
5418 return 0;
5419 }
5420 }
5421 break;
5422
5423 case KeyEvent.KEYCODE_TAB:
5424 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
5425 if (shouldAdvanceFocusOnTab()) {
5426 return 0;
5427 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005428 }
Gilles Debunne64e54a62010-09-07 19:07:17 -07005429 break;
5430
5431 // Has to be done on key down (and not on key up) to correctly be intercepted.
5432 case KeyEvent.KEYCODE_BACK:
Gilles Debunne2d373a12012-04-20 15:32:19 -07005433 if (mEditor != null && mEditor.mSelectionActionMode != null) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07005434 stopSelectionActionMode();
5435 return -1;
5436 }
5437 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005438 }
5439
Gilles Debunne2d373a12012-04-20 15:32:19 -07005440 if (mEditor != null && mEditor.mKeyListener != null) {
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08005441 resetErrorChangedFlag();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005442
5443 boolean doDown = true;
5444 if (otherEvent != null) {
5445 try {
5446 beginBatchEdit();
Gilles Debunne2d373a12012-04-20 15:32:19 -07005447 final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText,
5448 otherEvent);
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08005449 hideErrorIfUnchanged();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005450 doDown = false;
5451 if (handled) {
5452 return -1;
5453 }
5454 } catch (AbstractMethodError e) {
5455 // onKeyOther was added after 1.0, so if it isn't
5456 // implemented we need to try to dispatch as a regular down.
5457 } finally {
5458 endBatchEdit();
5459 }
5460 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005461
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005462 if (doDown) {
5463 beginBatchEdit();
Gilles Debunne2d373a12012-04-20 15:32:19 -07005464 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText,
5465 keyCode, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005466 endBatchEdit();
Gilles Debunne12ab6452011-01-30 12:08:25 -08005467 hideErrorIfUnchanged();
5468 if (handled) return 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005469 }
5470 }
5471
5472 // bug 650865: sometimes we get a key event before a layout.
5473 // don't try to move around if we don't know the layout.
5474
5475 if (mMovement != null && mLayout != null) {
5476 boolean doDown = true;
5477 if (otherEvent != null) {
5478 try {
5479 boolean handled = mMovement.onKeyOther(this, (Spannable) mText,
5480 otherEvent);
5481 doDown = false;
5482 if (handled) {
5483 return -1;
5484 }
5485 } catch (AbstractMethodError e) {
5486 // onKeyOther was added after 1.0, so if it isn't
5487 // implemented we need to try to dispatch as a regular down.
5488 }
5489 }
5490 if (doDown) {
5491 if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event))
5492 return 2;
5493 }
5494 }
5495
5496 return 0;
5497 }
5498
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08005499 /**
5500 * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
5501 * can be recorded.
5502 * @hide
5503 */
5504 public void resetErrorChangedFlag() {
5505 /*
5506 * Keep track of what the error was before doing the input
5507 * so that if an input filter changed the error, we leave
5508 * that error showing. Otherwise, we take down whatever
5509 * error was showing when the user types something.
5510 */
Gilles Debunne2d373a12012-04-20 15:32:19 -07005511 if (mEditor != null) mEditor.mErrorWasChanged = false;
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08005512 }
5513
5514 /**
5515 * @hide
5516 */
5517 public void hideErrorIfUnchanged() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005518 if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) {
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08005519 setError(null, null);
5520 }
5521 }
5522
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005523 @Override
5524 public boolean onKeyUp(int keyCode, KeyEvent event) {
5525 if (!isEnabled()) {
5526 return super.onKeyUp(keyCode, event);
5527 }
5528
5529 switch (keyCode) {
5530 case KeyEvent.KEYCODE_DPAD_CENTER:
Jeff Brown4e6319b2010-12-13 10:36:51 -08005531 if (event.hasNoModifiers()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005532 /*
5533 * If there is a click listener, just call through to
5534 * super, which will invoke it.
5535 *
Jeff Brown4e6319b2010-12-13 10:36:51 -08005536 * If there isn't a click listener, try to show the soft
5537 * input method. (It will also
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005538 * call performClick(), but that won't do anything in
5539 * this case.)
5540 */
Gilles Debunne06a8e9b2011-12-08 10:39:39 -08005541 if (!hasOnClickListeners()) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08005542 if (mMovement != null && mText instanceof Editable
5543 && mLayout != null && onCheckIsTextEditor()) {
Gilles Debunne17d31de2011-01-27 11:02:18 -08005544 InputMethodManager imm = InputMethodManager.peekInstance();
satoka67a3cf2011-09-07 17:14:03 +09005545 viewClicked(imm);
Gilles Debunne3473b2b2012-04-20 16:21:10 -07005546 if (imm != null && getShowSoftInputOnFocus()) {
satok863fcd62011-06-21 17:38:02 +09005547 imm.showSoftInput(this, 0);
5548 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08005549 }
5550 }
5551 }
5552 return super.onKeyUp(keyCode, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005553
Jeff Brown4e6319b2010-12-13 10:36:51 -08005554 case KeyEvent.KEYCODE_ENTER:
Jeff Brown4e6319b2010-12-13 10:36:51 -08005555 if (event.hasNoModifiers()) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005556 if (mEditor != null && mEditor.mInputContentType != null
5557 && mEditor.mInputContentType.onEditorActionListener != null
5558 && mEditor.mInputContentType.enterDown) {
5559 mEditor.mInputContentType.enterDown = false;
5560 if (mEditor.mInputContentType.onEditorActionListener.onEditorAction(
Jeff Brown4e6319b2010-12-13 10:36:51 -08005561 this, EditorInfo.IME_NULL, event)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005562 return true;
5563 }
5564 }
5565
Jeff Brown4e6319b2010-12-13 10:36:51 -08005566 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
5567 || shouldAdvanceFocusOnEnter()) {
5568 /*
5569 * If there is a click listener, just call through to
5570 * super, which will invoke it.
5571 *
5572 * If there isn't a click listener, try to advance focus,
5573 * but still call through to super, which will reset the
5574 * pressed state and longpress state. (It will also
5575 * call performClick(), but that won't do anything in
5576 * this case.)
5577 */
Gilles Debunne06a8e9b2011-12-08 10:39:39 -08005578 if (!hasOnClickListeners()) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08005579 View v = focusSearch(FOCUS_DOWN);
5580
5581 if (v != null) {
5582 if (!v.requestFocus(FOCUS_DOWN)) {
5583 throw new IllegalStateException(
5584 "focus search returned a view " +
5585 "that wasn't able to take focus!");
5586 }
5587
5588 /*
5589 * Return true because we handled the key; super
5590 * will return false because there was no click
5591 * listener.
5592 */
5593 super.onKeyUp(keyCode, event);
5594 return true;
5595 } else if ((event.getFlags()
5596 & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
5597 // No target for next focus, but make sure the IME
5598 // if this came from it.
5599 InputMethodManager imm = InputMethodManager.peekInstance();
Gilles Debunne17d31de2011-01-27 11:02:18 -08005600 if (imm != null && imm.isActive(this)) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08005601 imm.hideSoftInputFromWindow(getWindowToken(), 0);
5602 }
5603 }
5604 }
5605 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005606 return super.onKeyUp(keyCode, event);
5607 }
Gilles Debunne64e54a62010-09-07 19:07:17 -07005608 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005609 }
5610
Gilles Debunne2d373a12012-04-20 15:32:19 -07005611 if (mEditor != null && mEditor.mKeyListener != null)
5612 if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event))
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005613 return true;
5614
5615 if (mMovement != null && mLayout != null)
5616 if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event))
5617 return true;
5618
5619 return super.onKeyUp(keyCode, event);
5620 }
5621
Gilles Debunnec1714022012-01-17 13:59:23 -08005622 @Override
5623 public boolean onCheckIsTextEditor() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005624 return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005625 }
Gilles Debunneb062e812011-09-27 14:58:37 -07005626
Gilles Debunnec1714022012-01-17 13:59:23 -08005627 @Override
5628 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
Janos Levai042856c2010-10-15 02:53:58 +03005629 if (onCheckIsTextEditor() && isEnabled()) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005630 mEditor.createInputMethodStateIfNeeded();
Gilles Debunne60e21862012-01-30 15:04:14 -08005631 outAttrs.inputType = getInputType();
Gilles Debunne2d373a12012-04-20 15:32:19 -07005632 if (mEditor.mInputContentType != null) {
5633 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
5634 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
5635 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
5636 outAttrs.actionId = mEditor.mInputContentType.imeActionId;
5637 outAttrs.extras = mEditor.mInputContentType.extras;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005638 } else {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005639 outAttrs.imeOptions = EditorInfo.IME_NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005640 }
Dianne Hackborndea3ef72010-10-28 14:24:22 -07005641 if (focusSearch(FOCUS_DOWN) != null) {
5642 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
5643 }
5644 if (focusSearch(FOCUS_UP) != null) {
5645 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
5646 }
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005647 if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION)
5648 == EditorInfo.IME_ACTION_UNSPECIFIED) {
Dianne Hackborndea3ef72010-10-28 14:24:22 -07005649 if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005650 // An action has not been set, but the enter key will move to
5651 // the next focus, so set the action to that.
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005652 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
The Android Open Source Project4df24232009-03-05 14:34:35 -08005653 } else {
5654 // An action has not been set, and there is no focus to move
5655 // to, so let's just supply a "done" action.
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005656 outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
The Android Open Source Project4df24232009-03-05 14:34:35 -08005657 }
5658 if (!shouldAdvanceFocusOnEnter()) {
5659 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005660 }
5661 }
Gilles Debunne91a08cf2010-11-08 17:34:49 -08005662 if (isMultilineInputType(outAttrs.inputType)) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005663 // Multi-line text editors should always show an enter key.
5664 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
5665 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005666 outAttrs.hintText = mHint;
5667 if (mText instanceof Editable) {
5668 InputConnection ic = new EditableInputConnection(this);
Gilles Debunne05336272010-07-09 20:13:45 -07005669 outAttrs.initialSelStart = getSelectionStart();
5670 outAttrs.initialSelEnd = getSelectionEnd();
Gilles Debunne60e21862012-01-30 15:04:14 -08005671 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005672 return ic;
5673 }
5674 }
5675 return null;
5676 }
5677
5678 /**
5679 * If this TextView contains editable content, extract a portion of it
5680 * based on the information in <var>request</var> in to <var>outText</var>.
5681 * @return Returns true if the text was successfully extracted, else false.
5682 */
Gilles Debunned88876a2012-03-16 17:34:04 -07005683 public boolean extractText(ExtractedTextRequest request, ExtractedText outText) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07005684 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07005685 return mEditor.extractText(request, outText);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005686 }
Viktor Yakovel964be412010-02-17 08:35:57 +01005687
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005688 /**
5689 * This is used to remove all style-impacting spans from text before new
5690 * extracted text is being replaced into it, so that we don't have any
5691 * lingering spans applied during the replace.
5692 */
5693 static void removeParcelableSpans(Spannable spannable, int start, int end) {
5694 Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
5695 int i = spans.length;
5696 while (i > 0) {
5697 i--;
5698 spannable.removeSpan(spans[i]);
5699 }
5700 }
Gilles Debunned88876a2012-03-16 17:34:04 -07005701
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005702 /**
5703 * Apply to this text view the given extracted text, as previously
5704 * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
5705 */
5706 public void setExtractedText(ExtractedText text) {
5707 Editable content = getEditableText();
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005708 if (text.text != null) {
5709 if (content == null) {
5710 setText(text.text, TextView.BufferType.EDITABLE);
5711 } else if (text.partialStartOffset < 0) {
5712 removeParcelableSpans(content, 0, content.length());
5713 content.replace(0, content.length(), text.text);
5714 } else {
5715 final int N = content.length();
5716 int start = text.partialStartOffset;
5717 if (start > N) start = N;
5718 int end = text.partialEndOffset;
5719 if (end > N) end = N;
5720 removeParcelableSpans(content, start, end);
5721 content.replace(start, end, text.text);
5722 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005723 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005724
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005725 // Now set the selection position... make sure it is in range, to
5726 // avoid crashes. If this is a partial update, it is possible that
5727 // the underlying text may have changed, causing us problems here.
5728 // Also we just don't want to trust clients to do the right thing.
5729 Spannable sp = (Spannable)getText();
5730 final int N = sp.length();
5731 int start = text.selectionStart;
5732 if (start < 0) start = 0;
5733 else if (start > N) start = N;
5734 int end = text.selectionEnd;
5735 if (end < 0) end = 0;
5736 else if (end > N) end = N;
5737 Selection.setSelection(sp, start, end);
Gilles Debunne2d373a12012-04-20 15:32:19 -07005738
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005739 // Finally, update the selection mode.
5740 if ((text.flags&ExtractedText.FLAG_SELECTING) != 0) {
5741 MetaKeyKeyListener.startSelecting(this, sp);
5742 } else {
5743 MetaKeyKeyListener.stopSelecting(this, sp);
5744 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005745 }
Gilles Debunne98fb9ed2011-09-07 17:15:41 -07005746
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005747 /**
5748 * @hide
5749 */
5750 public void setExtracting(ExtractedTextRequest req) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005751 if (mEditor.mInputMethodState != null) {
5752 mEditor.mInputMethodState.mExtractedTextRequest = req;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005753 }
Gilles Debunne98fb9ed2011-09-07 17:15:41 -07005754 // This would stop a possible selection mode, but no such mode is started in case
5755 // extracted mode will start. Some text is selected though, and will trigger an action mode
5756 // in the extracted view.
Gilles Debunne2d373a12012-04-20 15:32:19 -07005757 mEditor.hideControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005758 }
Gilles Debunne98fb9ed2011-09-07 17:15:41 -07005759
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005760 /**
5761 * Called by the framework in response to a text completion from
5762 * the current input method, provided by it calling
5763 * {@link InputConnection#commitCompletion
5764 * InputConnection.commitCompletion()}. The default implementation does
5765 * nothing; text views that are supporting auto-completion should override
5766 * this to do their desired behavior.
5767 *
5768 * @param text The auto complete text the user has selected.
5769 */
5770 public void onCommitCompletion(CompletionInfo text) {
Gilles Debunned40cc3c2011-03-11 16:59:32 -08005771 // intentionally empty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005772 }
5773
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08005774 /**
5775 * Called by the framework in response to a text auto-correction (such as fixing a typo using a
5776 * a dictionnary) from the current input method, provided by it calling
5777 * {@link InputConnection#commitCorrection} InputConnection.commitCorrection()}. The default
5778 * implementation flashes the background of the corrected word to provide feedback to the user.
5779 *
5780 * @param info The auto correct info about the text that was corrected.
5781 */
5782 public void onCommitCorrection(CorrectionInfo info) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005783 if (mEditor != null) mEditor.onCommitCorrection(info);
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08005784 }
5785
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005786 public void beginBatchEdit() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005787 if (mEditor != null) mEditor.beginBatchEdit();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005788 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005789
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005790 public void endBatchEdit() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005791 if (mEditor != null) mEditor.endBatchEdit();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005792 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005793
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005794 /**
5795 * Called by the framework in response to a request to begin a batch
5796 * of edit operations through a call to link {@link #beginBatchEdit()}.
5797 */
5798 public void onBeginBatchEdit() {
Gilles Debunned40cc3c2011-03-11 16:59:32 -08005799 // intentionally empty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005800 }
Gilles Debunne60e21862012-01-30 15:04:14 -08005801
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005802 /**
5803 * Called by the framework in response to a request to end a batch
5804 * of edit operations through a call to link {@link #endBatchEdit}.
5805 */
5806 public void onEndBatchEdit() {
Gilles Debunned40cc3c2011-03-11 16:59:32 -08005807 // intentionally empty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005808 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005809
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005810 /**
5811 * Called by the framework in response to a private command from the
5812 * current method, provided by it calling
5813 * {@link InputConnection#performPrivateCommand
5814 * InputConnection.performPrivateCommand()}.
5815 *
5816 * @param action The action name of the command.
5817 * @param data Any additional data for the command. This may be null.
5818 * @return Return true if you handled the command, else false.
5819 */
5820 public boolean onPrivateIMECommand(String action, Bundle data) {
5821 return false;
5822 }
5823
5824 private void nullLayouts() {
5825 if (mLayout instanceof BoringLayout && mSavedLayout == null) {
5826 mSavedLayout = (BoringLayout) mLayout;
5827 }
5828 if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
5829 mSavedHintLayout = (BoringLayout) mHintLayout;
5830 }
5831
Adam Powell282e3772011-08-30 16:51:11 -07005832 mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
Gilles Debunne77f18b02010-10-22 14:28:25 -07005833
Fabrice Di Megliod4c3b8e2011-11-09 18:04:07 -08005834 mBoring = mHintBoring = null;
5835
Gilles Debunne77f18b02010-10-22 14:28:25 -07005836 // Since it depends on the value of mLayout
Gilles Debunne2d373a12012-04-20 15:32:19 -07005837 if (mEditor != null) mEditor.prepareCursorControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005838 }
5839
5840 /**
5841 * Make a new Layout based on the already-measured size of the view,
5842 * on the assumption that it was measured correctly at some point.
5843 */
5844 private void assumeLayout() {
5845 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
5846
5847 if (width < 1) {
5848 width = 0;
5849 }
5850
5851 int physicalWidth = width;
5852
5853 if (mHorizontallyScrolling) {
Jeff Brown033a0012011-11-11 15:30:16 -08005854 width = VERY_WIDE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005855 }
5856
5857 makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
5858 physicalWidth, false);
5859 }
5860
Doug Feltc0ccf0c2011-06-23 16:13:18 -07005861 private Layout.Alignment getLayoutAlignment() {
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08005862 Layout.Alignment alignment;
5863 switch (getTextAlignment()) {
5864 case TEXT_ALIGNMENT_GRAVITY:
5865 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
5866 case Gravity.START:
5867 alignment = Layout.Alignment.ALIGN_NORMAL;
5868 break;
5869 case Gravity.END:
5870 alignment = Layout.Alignment.ALIGN_OPPOSITE;
5871 break;
5872 case Gravity.LEFT:
5873 alignment = Layout.Alignment.ALIGN_LEFT;
5874 break;
5875 case Gravity.RIGHT:
5876 alignment = Layout.Alignment.ALIGN_RIGHT;
5877 break;
5878 case Gravity.CENTER_HORIZONTAL:
5879 alignment = Layout.Alignment.ALIGN_CENTER;
5880 break;
5881 default:
5882 alignment = Layout.Alignment.ALIGN_NORMAL;
5883 break;
5884 }
5885 break;
5886 case TEXT_ALIGNMENT_TEXT_START:
5887 alignment = Layout.Alignment.ALIGN_NORMAL;
5888 break;
5889 case TEXT_ALIGNMENT_TEXT_END:
5890 alignment = Layout.Alignment.ALIGN_OPPOSITE;
5891 break;
5892 case TEXT_ALIGNMENT_CENTER:
5893 alignment = Layout.Alignment.ALIGN_CENTER;
5894 break;
5895 case TEXT_ALIGNMENT_VIEW_START:
5896 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
5897 Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
5898 break;
5899 case TEXT_ALIGNMENT_VIEW_END:
5900 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
5901 Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
5902 break;
5903 case TEXT_ALIGNMENT_INHERIT:
5904 // This should never happen as we have already resolved the text alignment
5905 // but better safe than sorry so we just fall through
5906 default:
5907 alignment = Layout.Alignment.ALIGN_NORMAL;
5908 break;
Doug Feltc0ccf0c2011-06-23 16:13:18 -07005909 }
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08005910 return alignment;
Doug Feltc0ccf0c2011-06-23 16:13:18 -07005911 }
5912
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005913 /**
5914 * The width passed in is now the desired layout width,
5915 * not the full view width with padding.
5916 * {@hide}
5917 */
Gilles Debunne287d6c62011-10-05 18:22:11 -07005918 protected void makeNewLayout(int wantWidth, int hintWidth,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005919 BoringLayout.Metrics boring,
5920 BoringLayout.Metrics hintBoring,
5921 int ellipsisWidth, boolean bringIntoView) {
5922 stopMarquee();
5923
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07005924 // Update "old" cached values
5925 mOldMaximum = mMaximum;
5926 mOldMaxMode = mMaxMode;
5927
Gilles Debunne83051b82012-02-24 20:01:13 -08005928 mHighlightPathBogus = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005929
Gilles Debunne287d6c62011-10-05 18:22:11 -07005930 if (wantWidth < 0) {
5931 wantWidth = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005932 }
5933 if (hintWidth < 0) {
5934 hintWidth = 0;
5935 }
5936
Doug Feltc0ccf0c2011-06-23 16:13:18 -07005937 Layout.Alignment alignment = getLayoutAlignment();
Raph Levienf5cf6c92013-04-12 11:31:31 -07005938 final boolean testDirChange = mSingleLine && mLayout != null &&
5939 (alignment == Layout.Alignment.ALIGN_NORMAL ||
5940 alignment == Layout.Alignment.ALIGN_OPPOSITE);
5941 int oldDir = 0;
5942 if (testDirChange) oldDir = mLayout.getParagraphDirection(0);
Gilles Debunne60e21862012-01-30 15:04:14 -08005943 boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
Adam Powell282e3772011-08-30 16:51:11 -07005944 final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE &&
5945 mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
5946 TruncateAt effectiveEllipsize = mEllipsize;
5947 if (mEllipsize == TruncateAt.MARQUEE &&
5948 mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
Fabrice Di Megliocb332642011-09-23 19:08:04 -07005949 effectiveEllipsize = TruncateAt.END_SMALL;
Adam Powell282e3772011-08-30 16:51:11 -07005950 }
Romain Guy4dc4f732009-06-19 15:16:40 -07005951
Doug Feltcb3791202011-07-07 11:57:48 -07005952 if (mTextDir == null) {
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07005953 mTextDir = getTextDirectionHeuristic();
Doug Feltcb3791202011-07-07 11:57:48 -07005954 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005955
Gilles Debunne287d6c62011-10-05 18:22:11 -07005956 mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
Adam Powell282e3772011-08-30 16:51:11 -07005957 effectiveEllipsize, effectiveEllipsize == mEllipsize);
5958 if (switchEllipsize) {
5959 TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE ?
5960 TruncateAt.END : TruncateAt.MARQUEE;
Gilles Debunne287d6c62011-10-05 18:22:11 -07005961 mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
Adam Powell282e3772011-08-30 16:51:11 -07005962 shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005963 }
5964
Romain Guy4dc4f732009-06-19 15:16:40 -07005965 shouldEllipsize = mEllipsize != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005966 mHintLayout = null;
5967
5968 if (mHint != null) {
Gilles Debunne287d6c62011-10-05 18:22:11 -07005969 if (shouldEllipsize) hintWidth = wantWidth;
Romain Guy4dc4f732009-06-19 15:16:40 -07005970
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005971 if (hintBoring == UNKNOWN_BORING) {
Doug Feltcb3791202011-07-07 11:57:48 -07005972 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005973 mHintBoring);
5974 if (hintBoring != null) {
5975 mHintBoring = hintBoring;
5976 }
5977 }
5978
5979 if (hintBoring != null) {
Romain Guy4dc4f732009-06-19 15:16:40 -07005980 if (hintBoring.width <= hintWidth &&
5981 (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005982 if (mSavedHintLayout != null) {
5983 mHintLayout = mSavedHintLayout.
5984 replaceOrMake(mHint, mTextPaint,
Romain Guy4dc4f732009-06-19 15:16:40 -07005985 hintWidth, alignment, mSpacingMult, mSpacingAdd,
5986 hintBoring, mIncludePad);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005987 } else {
5988 mHintLayout = BoringLayout.make(mHint, mTextPaint,
Romain Guy4dc4f732009-06-19 15:16:40 -07005989 hintWidth, alignment, mSpacingMult, mSpacingAdd,
5990 hintBoring, mIncludePad);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005991 }
5992
5993 mSavedHintLayout = (BoringLayout) mHintLayout;
Romain Guy4dc4f732009-06-19 15:16:40 -07005994 } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
5995 if (mSavedHintLayout != null) {
5996 mHintLayout = mSavedHintLayout.
5997 replaceOrMake(mHint, mTextPaint,
5998 hintWidth, alignment, mSpacingMult, mSpacingAdd,
5999 hintBoring, mIncludePad, mEllipsize,
6000 ellipsisWidth);
6001 } else {
6002 mHintLayout = BoringLayout.make(mHint, mTextPaint,
6003 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6004 hintBoring, mIncludePad, mEllipsize,
6005 ellipsisWidth);
6006 }
6007 } else if (shouldEllipsize) {
6008 mHintLayout = new StaticLayout(mHint,
6009 0, mHint.length(),
Doug Feltcb3791202011-07-07 11:57:48 -07006010 mTextPaint, hintWidth, alignment, mTextDir, mSpacingMult,
Romain Guy4dc4f732009-06-19 15:16:40 -07006011 mSpacingAdd, mIncludePad, mEllipsize,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07006012 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006013 } else {
6014 mHintLayout = new StaticLayout(mHint, mTextPaint,
Doug Feltcb3791202011-07-07 11:57:48 -07006015 hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006016 mIncludePad);
6017 }
Romain Guy4dc4f732009-06-19 15:16:40 -07006018 } else if (shouldEllipsize) {
6019 mHintLayout = new StaticLayout(mHint,
6020 0, mHint.length(),
Doug Feltcb3791202011-07-07 11:57:48 -07006021 mTextPaint, hintWidth, alignment, mTextDir, mSpacingMult,
Romain Guy4dc4f732009-06-19 15:16:40 -07006022 mSpacingAdd, mIncludePad, mEllipsize,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07006023 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006024 } else {
6025 mHintLayout = new StaticLayout(mHint, mTextPaint,
Doug Feltcb3791202011-07-07 11:57:48 -07006026 hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006027 mIncludePad);
6028 }
6029 }
6030
Raph Levienf5cf6c92013-04-12 11:31:31 -07006031 if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006032 registerForPreDraw();
6033 }
6034
6035 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
Romain Guy939151f2009-04-08 14:22:40 -07006036 if (!compressText(ellipsisWidth)) {
6037 final int height = mLayoutParams.height;
6038 // If the size of the view does not depend on the size of the text, try to
6039 // start the marquee immediately
Romain Guy980a9382010-01-08 15:06:28 -08006040 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
Romain Guy939151f2009-04-08 14:22:40 -07006041 startMarquee();
6042 } else {
6043 // Defer the start of the marquee until we know our width (see setFrame())
6044 mRestartMarquee = true;
6045 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006046 }
6047 }
Gilles Debunnef788a9f2010-07-22 10:17:23 -07006048
6049 // CursorControllers need a non-null mLayout
Gilles Debunne2d373a12012-04-20 15:32:19 -07006050 if (mEditor != null) mEditor.prepareCursorControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006051 }
6052
Gilles Debunne287d6c62011-10-05 18:22:11 -07006053 private Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
Adam Powell282e3772011-08-30 16:51:11 -07006054 Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
6055 boolean useSaved) {
6056 Layout result = null;
6057 if (mText instanceof Spannable) {
Gilles Debunne287d6c62011-10-05 18:22:11 -07006058 result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth,
Adam Powell282e3772011-08-30 16:51:11 -07006059 alignment, mTextDir, mSpacingMult,
Gilles Debunne60e21862012-01-30 15:04:14 -08006060 mSpacingAdd, mIncludePad, getKeyListener() == null ? effectiveEllipsize : null,
Fabrice Di Meglioad0b0512011-10-04 17:21:26 -07006061 ellipsisWidth);
Adam Powell282e3772011-08-30 16:51:11 -07006062 } else {
6063 if (boring == UNKNOWN_BORING) {
6064 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
6065 if (boring != null) {
6066 mBoring = boring;
6067 }
6068 }
6069
6070 if (boring != null) {
Gilles Debunne287d6c62011-10-05 18:22:11 -07006071 if (boring.width <= wantWidth &&
Adam Powell282e3772011-08-30 16:51:11 -07006072 (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
6073 if (useSaved && mSavedLayout != null) {
6074 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006075 wantWidth, alignment, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006076 boring, mIncludePad);
6077 } else {
6078 result = BoringLayout.make(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006079 wantWidth, alignment, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006080 boring, mIncludePad);
6081 }
6082
6083 if (useSaved) {
6084 mSavedLayout = (BoringLayout) result;
6085 }
Gilles Debunne287d6c62011-10-05 18:22:11 -07006086 } else if (shouldEllipsize && boring.width <= wantWidth) {
Adam Powell282e3772011-08-30 16:51:11 -07006087 if (useSaved && mSavedLayout != null) {
6088 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006089 wantWidth, alignment, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006090 boring, mIncludePad, effectiveEllipsize,
6091 ellipsisWidth);
6092 } else {
6093 result = BoringLayout.make(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006094 wantWidth, alignment, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006095 boring, mIncludePad, effectiveEllipsize,
6096 ellipsisWidth);
6097 }
6098 } else if (shouldEllipsize) {
6099 result = new StaticLayout(mTransformed,
6100 0, mTransformed.length(),
Gilles Debunne287d6c62011-10-05 18:22:11 -07006101 mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult,
Adam Powell282e3772011-08-30 16:51:11 -07006102 mSpacingAdd, mIncludePad, effectiveEllipsize,
6103 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6104 } else {
6105 result = new StaticLayout(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006106 wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006107 mIncludePad);
6108 }
6109 } else if (shouldEllipsize) {
6110 result = new StaticLayout(mTransformed,
6111 0, mTransformed.length(),
Gilles Debunne287d6c62011-10-05 18:22:11 -07006112 mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult,
Adam Powell282e3772011-08-30 16:51:11 -07006113 mSpacingAdd, mIncludePad, effectiveEllipsize,
6114 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6115 } else {
6116 result = new StaticLayout(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006117 wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006118 mIncludePad);
6119 }
6120 }
6121 return result;
6122 }
6123
Romain Guy939151f2009-04-08 14:22:40 -07006124 private boolean compressText(float width) {
Romain Guy2bffd262010-09-12 17:40:02 -07006125 if (isHardwareAccelerated()) return false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07006126
Romain Guy3373ed62009-05-04 14:13:32 -07006127 // Only compress the text if it hasn't been compressed by the previous pass
6128 if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX &&
6129 mTextPaint.getTextScaleX() == 1.0f) {
Romain Guy939151f2009-04-08 14:22:40 -07006130 final float textWidth = mLayout.getLineWidth(0);
Romain Guy3373ed62009-05-04 14:13:32 -07006131 final float overflow = (textWidth + 1.0f - width) / width;
Romain Guy939151f2009-04-08 14:22:40 -07006132 if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
6133 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
6134 post(new Runnable() {
6135 public void run() {
6136 requestLayout();
6137 }
6138 });
6139 return true;
6140 }
6141 }
6142
6143 return false;
6144 }
6145
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006146 private static int desired(Layout layout) {
6147 int n = layout.getLineCount();
6148 CharSequence text = layout.getText();
6149 float max = 0;
6150
6151 // if any line was wrapped, we can't use it.
6152 // but it's ok for the last line not to have a newline
6153
6154 for (int i = 0; i < n - 1; i++) {
6155 if (text.charAt(layout.getLineEnd(i) - 1) != '\n')
6156 return -1;
6157 }
6158
6159 for (int i = 0; i < n; i++) {
6160 max = Math.max(max, layout.getLineWidth(i));
6161 }
6162
6163 return (int) FloatMath.ceil(max);
6164 }
6165
6166 /**
6167 * Set whether the TextView includes extra top and bottom padding to make
6168 * room for accents that go above the normal ascent and descent.
6169 * The default is true.
6170 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07006171 * @see #getIncludeFontPadding()
6172 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006173 * @attr ref android.R.styleable#TextView_includeFontPadding
6174 */
6175 public void setIncludeFontPadding(boolean includepad) {
Gilles Debunne22378292011-08-12 10:38:52 -07006176 if (mIncludePad != includepad) {
6177 mIncludePad = includepad;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006178
Gilles Debunne22378292011-08-12 10:38:52 -07006179 if (mLayout != null) {
6180 nullLayouts();
6181 requestLayout();
6182 invalidate();
6183 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006184 }
6185 }
6186
Gilles Debunnef03acef2012-04-30 19:26:19 -07006187 /**
6188 * Gets whether the TextView includes extra top and bottom padding to make
6189 * room for accents that go above the normal ascent and descent.
6190 *
6191 * @see #setIncludeFontPadding(boolean)
6192 *
6193 * @attr ref android.R.styleable#TextView_includeFontPadding
6194 */
6195 public boolean getIncludeFontPadding() {
6196 return mIncludePad;
6197 }
6198
Romain Guy4dc4f732009-06-19 15:16:40 -07006199 private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006200
6201 @Override
6202 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
6203 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
6204 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
6205 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
6206 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
6207
6208 int width;
6209 int height;
6210
6211 BoringLayout.Metrics boring = UNKNOWN_BORING;
6212 BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
6213
Doug Feltcb3791202011-07-07 11:57:48 -07006214 if (mTextDir == null) {
Fabrice Di Meglioa423f502013-05-14 13:20:32 -07006215 mTextDir = getTextDirectionHeuristic();
Doug Feltcb3791202011-07-07 11:57:48 -07006216 }
6217
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006218 int des = -1;
6219 boolean fromexisting = false;
6220
6221 if (widthMode == MeasureSpec.EXACTLY) {
6222 // Parent has told us how big to be. So be it.
6223 width = widthSize;
6224 } else {
6225 if (mLayout != null && mEllipsize == null) {
6226 des = desired(mLayout);
6227 }
6228
6229 if (des < 0) {
Doug Feltcb3791202011-07-07 11:57:48 -07006230 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006231 if (boring != null) {
6232 mBoring = boring;
6233 }
6234 } else {
6235 fromexisting = true;
6236 }
6237
6238 if (boring == null || boring == UNKNOWN_BORING) {
6239 if (des < 0) {
Romain Guy4dc4f732009-06-19 15:16:40 -07006240 des = (int) FloatMath.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006241 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006242 width = des;
6243 } else {
6244 width = boring.width;
6245 }
6246
6247 final Drawables dr = mDrawables;
6248 if (dr != null) {
6249 width = Math.max(width, dr.mDrawableWidthTop);
6250 width = Math.max(width, dr.mDrawableWidthBottom);
6251 }
6252
6253 if (mHint != null) {
6254 int hintDes = -1;
6255 int hintWidth;
6256
Romain Guy4dc4f732009-06-19 15:16:40 -07006257 if (mHintLayout != null && mEllipsize == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006258 hintDes = desired(mHintLayout);
6259 }
6260
6261 if (hintDes < 0) {
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006262 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006263 if (hintBoring != null) {
6264 mHintBoring = hintBoring;
6265 }
6266 }
6267
6268 if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
6269 if (hintDes < 0) {
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006270 hintDes = (int) FloatMath.ceil(Layout.getDesiredWidth(mHint, mTextPaint));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006271 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006272 hintWidth = hintDes;
6273 } else {
6274 hintWidth = hintBoring.width;
6275 }
6276
6277 if (hintWidth > width) {
6278 width = hintWidth;
6279 }
6280 }
6281
6282 width += getCompoundPaddingLeft() + getCompoundPaddingRight();
6283
6284 if (mMaxWidthMode == EMS) {
6285 width = Math.min(width, mMaxWidth * getLineHeight());
6286 } else {
6287 width = Math.min(width, mMaxWidth);
6288 }
6289
6290 if (mMinWidthMode == EMS) {
6291 width = Math.max(width, mMinWidth * getLineHeight());
6292 } else {
6293 width = Math.max(width, mMinWidth);
6294 }
6295
6296 // Check against our minimum width
6297 width = Math.max(width, getSuggestedMinimumWidth());
6298
6299 if (widthMode == MeasureSpec.AT_MOST) {
6300 width = Math.min(widthSize, width);
6301 }
6302 }
6303
6304 int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
6305 int unpaddedWidth = want;
Gilles Debunne9a80a652011-01-31 12:56:07 -08006306
Jeff Brown033a0012011-11-11 15:30:16 -08006307 if (mHorizontallyScrolling) want = VERY_WIDE;
Gilles Debunne9a80a652011-01-31 12:56:07 -08006308
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006309 int hintWant = want;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07006310 int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006311
6312 if (mLayout == null) {
6313 makeNewLayout(want, hintWant, boring, hintBoring,
Romain Guy4dc4f732009-06-19 15:16:40 -07006314 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006315 } else {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07006316 final boolean layoutChanged = (mLayout.getWidth() != want) ||
6317 (hintWidth != hintWant) ||
6318 (mLayout.getEllipsizedWidth() !=
6319 width - getCompoundPaddingLeft() - getCompoundPaddingRight());
6320
6321 final boolean widthChanged = (mHint == null) &&
6322 (mEllipsize == null) &&
6323 (want > mLayout.getWidth()) &&
6324 (mLayout instanceof BoringLayout || (fromexisting && des >= 0 && des <= want));
6325
6326 final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
6327
6328 if (layoutChanged || maximumChanged) {
6329 if (!maximumChanged && widthChanged) {
6330 mLayout.increaseWidthTo(want);
6331 } else {
6332 makeNewLayout(want, hintWant, boring, hintBoring,
6333 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
6334 }
6335 } else {
6336 // Nothing has changed
6337 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006338 }
6339
6340 if (heightMode == MeasureSpec.EXACTLY) {
6341 // Parent has told us how big to be. So be it.
6342 height = heightSize;
6343 mDesiredHeightAtMeasure = -1;
6344 } else {
6345 int desired = getDesiredHeight();
6346
6347 height = desired;
6348 mDesiredHeightAtMeasure = desired;
6349
6350 if (heightMode == MeasureSpec.AT_MOST) {
Christoffer Gurell1d05c7c2009-10-12 15:53:39 +02006351 height = Math.min(desired, heightSize);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006352 }
6353 }
6354
Romain Guy4dc4f732009-06-19 15:16:40 -07006355 int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006356 if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
Romain Guy4dc4f732009-06-19 15:16:40 -07006357 unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006358 }
6359
6360 /*
6361 * We didn't let makeNewLayout() register to bring the cursor into view,
6362 * so do it here if there is any possibility that it is needed.
6363 */
6364 if (mMovement != null ||
6365 mLayout.getWidth() > unpaddedWidth ||
6366 mLayout.getHeight() > unpaddedHeight) {
6367 registerForPreDraw();
6368 } else {
6369 scrollTo(0, 0);
6370 }
6371
6372 setMeasuredDimension(width, height);
6373 }
6374
6375 private int getDesiredHeight() {
Romain Guy4dc4f732009-06-19 15:16:40 -07006376 return Math.max(
6377 getDesiredHeight(mLayout, true),
6378 getDesiredHeight(mHintLayout, mEllipsize != null));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006379 }
6380
6381 private int getDesiredHeight(Layout layout, boolean cap) {
6382 if (layout == null) {
6383 return 0;
6384 }
6385
6386 int linecount = layout.getLineCount();
6387 int pad = getCompoundPaddingTop() + getCompoundPaddingBottom();
6388 int desired = layout.getLineTop(linecount);
6389
6390 final Drawables dr = mDrawables;
6391 if (dr != null) {
6392 desired = Math.max(desired, dr.mDrawableHeightLeft);
6393 desired = Math.max(desired, dr.mDrawableHeightRight);
6394 }
6395
6396 desired += pad;
6397
6398 if (mMaxMode == LINES) {
6399 /*
6400 * Don't cap the hint to a certain number of lines.
6401 * (Do cap it, though, if we have a maximum pixel height.)
6402 */
6403 if (cap) {
6404 if (linecount > mMaximum) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -08006405 desired = layout.getLineTop(mMaximum);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006406
6407 if (dr != null) {
6408 desired = Math.max(desired, dr.mDrawableHeightLeft);
6409 desired = Math.max(desired, dr.mDrawableHeightRight);
6410 }
6411
6412 desired += pad;
6413 linecount = mMaximum;
6414 }
6415 }
6416 } else {
6417 desired = Math.min(desired, mMaximum);
6418 }
6419
6420 if (mMinMode == LINES) {
6421 if (linecount < mMinimum) {
6422 desired += getLineHeight() * (mMinimum - linecount);
6423 }
6424 } else {
6425 desired = Math.max(desired, mMinimum);
6426 }
6427
6428 // Check against our minimum height
6429 desired = Math.max(desired, getSuggestedMinimumHeight());
6430
6431 return desired;
6432 }
6433
6434 /**
6435 * Check whether a change to the existing text layout requires a
6436 * new view layout.
6437 */
6438 private void checkForResize() {
6439 boolean sizeChanged = false;
6440
6441 if (mLayout != null) {
6442 // Check if our width changed
6443 if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
6444 sizeChanged = true;
6445 invalidate();
6446 }
6447
6448 // Check if our height changed
6449 if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
6450 int desiredHeight = getDesiredHeight();
6451
6452 if (desiredHeight != this.getHeight()) {
6453 sizeChanged = true;
6454 }
Romain Guy980a9382010-01-08 15:06:28 -08006455 } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006456 if (mDesiredHeightAtMeasure >= 0) {
6457 int desiredHeight = getDesiredHeight();
6458
6459 if (desiredHeight != mDesiredHeightAtMeasure) {
6460 sizeChanged = true;
6461 }
6462 }
6463 }
6464 }
6465
6466 if (sizeChanged) {
6467 requestLayout();
6468 // caller will have already invalidated
6469 }
6470 }
6471
6472 /**
6473 * Check whether entirely new text requires a new view layout
6474 * or merely a new text layout.
6475 */
6476 private void checkForRelayout() {
6477 // If we have a fixed width, we can just swap in a new text layout
6478 // if the text height stays the same or if the view height is fixed.
6479
6480 if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||
6481 (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&
6482 (mHint == null || mHintLayout != null) &&
6483 (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
6484 // Static width, so try making a new text layout.
6485
6486 int oldht = mLayout.getHeight();
6487 int want = mLayout.getWidth();
6488 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
6489
6490 /*
6491 * No need to bring the text into view, since the size is not
6492 * changing (unless we do the requestLayout(), in which case it
6493 * will happen at measure).
6494 */
6495 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
Romain Guye1e0dc82009-11-03 17:21:04 -08006496 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
6497 false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006498
Romain Guye1e0dc82009-11-03 17:21:04 -08006499 if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
6500 // In a fixed-height view, so use our new text layout.
6501 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
Romain Guy980a9382010-01-08 15:06:28 -08006502 mLayoutParams.height != LayoutParams.MATCH_PARENT) {
Romain Guye1e0dc82009-11-03 17:21:04 -08006503 invalidate();
6504 return;
6505 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006506
Romain Guye1e0dc82009-11-03 17:21:04 -08006507 // Dynamic height, but height has stayed the same,
6508 // so use our new text layout.
6509 if (mLayout.getHeight() == oldht &&
6510 (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
6511 invalidate();
6512 return;
6513 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006514 }
6515
6516 // We lose: the height has changed and we have a dynamic height.
6517 // Request a new view layout using our new text layout.
6518 requestLayout();
6519 invalidate();
6520 } else {
6521 // Dynamic width, so we have no choice but to request a new
6522 // view layout with a new text layout.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006523 nullLayouts();
6524 requestLayout();
6525 invalidate();
6526 }
6527 }
6528
Gilles Debunne954325e2012-01-25 11:57:06 -08006529 @Override
6530 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
6531 super.onLayout(changed, left, top, right, bottom);
Raph Levienf5c1a872012-10-15 17:22:26 -07006532 if (mDeferScroll >= 0) {
6533 int curs = mDeferScroll;
6534 mDeferScroll = -1;
Raph Levien8b179692012-10-16 14:32:47 -07006535 bringPointIntoView(Math.min(curs, mText.length()));
Raph Levienf5c1a872012-10-15 17:22:26 -07006536 }
Gilles Debunne954325e2012-01-25 11:57:06 -08006537 }
6538
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006539 private boolean isShowingHint() {
6540 return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint);
6541 }
6542
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006543 /**
6544 * Returns true if anything changed.
6545 */
6546 private boolean bringTextIntoView() {
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006547 Layout layout = isShowingHint() ? mHintLayout : mLayout;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006548 int line = 0;
6549 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006550 line = layout.getLineCount() - 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006551 }
6552
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006553 Layout.Alignment a = layout.getParagraphAlignment(line);
6554 int dir = layout.getParagraphDirection(line);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006555 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6556 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006557 int ht = layout.getHeight();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006558
6559 int scrollx, scrolly;
6560
Doug Felt25b9f422011-07-11 13:48:37 -07006561 // Convert to left, center, or right alignment.
6562 if (a == Layout.Alignment.ALIGN_NORMAL) {
6563 a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_LEFT :
6564 Layout.Alignment.ALIGN_RIGHT;
6565 } else if (a == Layout.Alignment.ALIGN_OPPOSITE){
6566 a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_RIGHT :
6567 Layout.Alignment.ALIGN_LEFT;
6568 }
6569
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006570 if (a == Layout.Alignment.ALIGN_CENTER) {
6571 /*
6572 * Keep centered if possible, or, if it is too wide to fit,
6573 * keep leading edge in view.
6574 */
6575
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006576 int left = (int) FloatMath.floor(layout.getLineLeft(line));
6577 int right = (int) FloatMath.ceil(layout.getLineRight(line));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006578
6579 if (right - left < hspace) {
6580 scrollx = (right + left) / 2 - hspace / 2;
6581 } else {
6582 if (dir < 0) {
6583 scrollx = right - hspace;
6584 } else {
6585 scrollx = left;
6586 }
6587 }
Fabrice Di Megliod2b5d1c2011-07-13 19:38:17 -07006588 } else if (a == Layout.Alignment.ALIGN_RIGHT) {
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006589 int right = (int) FloatMath.ceil(layout.getLineRight(line));
Doug Felt25b9f422011-07-11 13:48:37 -07006590 scrollx = right - hspace;
Fabrice Di Megliod2b5d1c2011-07-13 19:38:17 -07006591 } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006592 scrollx = (int) FloatMath.floor(layout.getLineLeft(line));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006593 }
6594
6595 if (ht < vspace) {
6596 scrolly = 0;
6597 } else {
6598 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
6599 scrolly = ht - vspace;
6600 } else {
6601 scrolly = 0;
6602 }
6603 }
6604
6605 if (scrollx != mScrollX || scrolly != mScrollY) {
6606 scrollTo(scrollx, scrolly);
6607 return true;
6608 } else {
6609 return false;
6610 }
6611 }
6612
6613 /**
6614 * Move the point, specified by the offset, into the view if it is needed.
6615 * This has to be called after layout. Returns true if anything changed.
6616 */
6617 public boolean bringPointIntoView(int offset) {
Raph Levienf5c1a872012-10-15 17:22:26 -07006618 if (isLayoutRequested()) {
6619 mDeferScroll = offset;
6620 return false;
6621 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006622 boolean changed = false;
6623
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006624 Layout layout = isShowingHint() ? mHintLayout: mLayout;
Gilles Debunne176ee3d2011-07-16 13:28:41 -07006625
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006626 if (layout == null) return changed;
6627
6628 int line = layout.getLineForOffset(offset);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006629
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006630 int grav;
6631
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006632 switch (layout.getParagraphAlignment(line)) {
Doug Felt25b9f422011-07-11 13:48:37 -07006633 case ALIGN_LEFT:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006634 grav = 1;
6635 break;
Doug Felt25b9f422011-07-11 13:48:37 -07006636 case ALIGN_RIGHT:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006637 grav = -1;
6638 break;
Doug Felt25b9f422011-07-11 13:48:37 -07006639 case ALIGN_NORMAL:
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006640 grav = layout.getParagraphDirection(line);
Doug Felt25b9f422011-07-11 13:48:37 -07006641 break;
6642 case ALIGN_OPPOSITE:
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006643 grav = -layout.getParagraphDirection(line);
Doug Felt25b9f422011-07-11 13:48:37 -07006644 break;
6645 case ALIGN_CENTER:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006646 default:
6647 grav = 0;
Doug Felt25b9f422011-07-11 13:48:37 -07006648 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006649 }
6650
Raph Levienafe8e9b2012-12-19 16:09:32 -08006651 // We only want to clamp the cursor to fit within the layout width
6652 // in left-to-right modes, because in a right to left alignment,
6653 // we want to scroll to keep the line-right on the screen, as other
6654 // lines are likely to have text flush with the right margin, which
6655 // we want to keep visible.
6656 // A better long-term solution would probably be to measure both
6657 // the full line and a blank-trimmed version, and, for example, use
6658 // the latter measurement for centering and right alignment, but for
6659 // the time being we only implement the cursor clamping in left to
6660 // right where it is most likely to be annoying.
6661 final boolean clamped = grav > 0;
6662 // FIXME: Is it okay to truncate this, or should we round?
6663 final int x = (int)layout.getPrimaryHorizontal(offset, clamped);
6664 final int top = layout.getLineTop(line);
6665 final int bottom = layout.getLineTop(line + 1);
6666
6667 int left = (int) FloatMath.floor(layout.getLineLeft(line));
6668 int right = (int) FloatMath.ceil(layout.getLineRight(line));
6669 int ht = layout.getHeight();
6670
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006671 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6672 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
Raph Levienafe8e9b2012-12-19 16:09:32 -08006673 if (!mHorizontallyScrolling && right - left > hspace && right > x) {
6674 // If cursor has been clamped, make sure we don't scroll.
6675 right = Math.max(x, left + hspace);
6676 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006677
6678 int hslack = (bottom - top) / 2;
6679 int vslack = hslack;
6680
6681 if (vslack > vspace / 4)
6682 vslack = vspace / 4;
6683 if (hslack > hspace / 4)
6684 hslack = hspace / 4;
6685
6686 int hs = mScrollX;
6687 int vs = mScrollY;
6688
6689 if (top - vs < vslack)
6690 vs = top - vslack;
6691 if (bottom - vs > vspace - vslack)
6692 vs = bottom - (vspace - vslack);
6693 if (ht - vs < vspace)
6694 vs = ht - vspace;
6695 if (0 - vs > 0)
6696 vs = 0;
6697
6698 if (grav != 0) {
6699 if (x - hs < hslack) {
6700 hs = x - hslack;
6701 }
6702 if (x - hs > hspace - hslack) {
6703 hs = x - (hspace - hslack);
6704 }
6705 }
6706
6707 if (grav < 0) {
6708 if (left - hs > 0)
6709 hs = left;
6710 if (right - hs < hspace)
6711 hs = right - hspace;
6712 } else if (grav > 0) {
6713 if (right - hs < hspace)
6714 hs = right - hspace;
6715 if (left - hs > 0)
6716 hs = left;
6717 } else /* grav == 0 */ {
6718 if (right - left <= hspace) {
6719 /*
6720 * If the entire text fits, center it exactly.
6721 */
6722 hs = left - (hspace - (right - left)) / 2;
6723 } else if (x > right - hslack) {
6724 /*
6725 * If we are near the right edge, keep the right edge
6726 * at the edge of the view.
6727 */
6728 hs = right - hspace;
6729 } else if (x < left + hslack) {
6730 /*
6731 * If we are near the left edge, keep the left edge
6732 * at the edge of the view.
6733 */
6734 hs = left;
6735 } else if (left > hs) {
6736 /*
6737 * Is there whitespace visible at the left? Fix it if so.
6738 */
6739 hs = left;
6740 } else if (right < hs + hspace) {
6741 /*
6742 * Is there whitespace visible at the right? Fix it if so.
6743 */
6744 hs = right - hspace;
6745 } else {
6746 /*
6747 * Otherwise, float as needed.
6748 */
6749 if (x - hs < hslack) {
6750 hs = x - hslack;
6751 }
6752 if (x - hs > hspace - hslack) {
6753 hs = x - (hspace - hslack);
6754 }
6755 }
6756 }
6757
6758 if (hs != mScrollX || vs != mScrollY) {
6759 if (mScroller == null) {
6760 scrollTo(hs, vs);
6761 } else {
6762 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
6763 int dx = hs - mScrollX;
6764 int dy = vs - mScrollY;
6765
6766 if (duration > ANIMATED_SCROLL_GAP) {
6767 mScroller.startScroll(mScrollX, mScrollY, dx, dy);
Mike Cleronf116bf82009-09-27 19:14:12 -07006768 awakenScrollBars(mScroller.getDuration());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006769 invalidate();
6770 } else {
6771 if (!mScroller.isFinished()) {
6772 mScroller.abortAnimation();
6773 }
6774
6775 scrollBy(dx, dy);
6776 }
6777
6778 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
6779 }
6780
6781 changed = true;
6782 }
6783
6784 if (isFocused()) {
Gilles Debunne716dbf62011-03-07 18:12:10 -08006785 // This offsets because getInterestingRect() is in terms of viewport coordinates, but
6786 // requestRectangleOnScreen() is in terms of content coordinates.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006787
Dianne Hackborn70a3f672011-08-08 14:32:41 -07006788 // The offsets here are to ensure the rectangle we are using is
6789 // within our view bounds, in case the cursor is on the far left
6790 // or right. If it isn't withing the bounds, then this request
6791 // will be ignored.
Gilles Debunne60e21862012-01-30 15:04:14 -08006792 if (mTempRect == null) mTempRect = new Rect();
Dianne Hackborn70a3f672011-08-08 14:32:41 -07006793 mTempRect.set(x - 2, top, x + 2, bottom);
Gilles Debunne716dbf62011-03-07 18:12:10 -08006794 getInterestingRect(mTempRect, line);
6795 mTempRect.offset(mScrollX, mScrollY);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006796
Gilles Debunne716dbf62011-03-07 18:12:10 -08006797 if (requestRectangleOnScreen(mTempRect)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006798 changed = true;
6799 }
6800 }
6801
6802 return changed;
6803 }
6804
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006805 /**
6806 * Move the cursor, if needed, so that it is at an offset that is visible
6807 * to the user. This will not move the cursor if it represents more than
6808 * one character (a selection range). This will only work if the
6809 * TextView contains spannable text; otherwise it will do nothing.
Gilles Debunnebbb5d6e2010-06-21 16:21:51 -07006810 *
Gilles Debunne57f4e5b2010-06-21 16:21:51 -07006811 * @return True if the cursor was actually moved, false otherwise.
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006812 */
6813 public boolean moveCursorToVisibleOffset() {
6814 if (!(mText instanceof Spannable)) {
6815 return false;
6816 }
Gilles Debunne05336272010-07-09 20:13:45 -07006817 int start = getSelectionStart();
6818 int end = getSelectionEnd();
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006819 if (start != end) {
6820 return false;
6821 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006822
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006823 // First: make sure the line is visible on screen:
Gilles Debunne2d373a12012-04-20 15:32:19 -07006824
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006825 int line = mLayout.getLineForOffset(start);
6826
6827 final int top = mLayout.getLineTop(line);
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07006828 final int bottom = mLayout.getLineTop(line + 1);
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006829 final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
6830 int vslack = (bottom - top) / 2;
6831 if (vslack > vspace / 4)
6832 vslack = vspace / 4;
6833 final int vs = mScrollY;
6834
6835 if (top < (vs+vslack)) {
6836 line = mLayout.getLineForVertical(vs+vslack+(bottom-top));
6837 } else if (bottom > (vspace+vs-vslack)) {
6838 line = mLayout.getLineForVertical(vspace+vs-vslack-(bottom-top));
6839 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006840
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006841 // Next: make sure the character is visible on screen:
Gilles Debunne2d373a12012-04-20 15:32:19 -07006842
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006843 final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6844 final int hs = mScrollX;
6845 final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
6846 final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs);
Gilles Debunne2d373a12012-04-20 15:32:19 -07006847
Doug Feltc982f602010-05-25 11:51:40 -07006848 // line might contain bidirectional text
6849 final int lowChar = leftChar < rightChar ? leftChar : rightChar;
6850 final int highChar = leftChar > rightChar ? leftChar : rightChar;
6851
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006852 int newStart = start;
Doug Feltc982f602010-05-25 11:51:40 -07006853 if (newStart < lowChar) {
6854 newStart = lowChar;
6855 } else if (newStart > highChar) {
6856 newStart = highChar;
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006857 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006858
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006859 if (newStart != start) {
6860 Selection.setSelection((Spannable)mText, newStart);
6861 return true;
6862 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006863
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006864 return false;
6865 }
6866
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006867 @Override
6868 public void computeScroll() {
6869 if (mScroller != null) {
6870 if (mScroller.computeScrollOffset()) {
6871 mScrollX = mScroller.getCurrX();
6872 mScrollY = mScroller.getCurrY();
Romain Guy0fd89bf2011-01-26 15:41:30 -08006873 invalidateParentCaches();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006874 postInvalidate(); // So we draw again
6875 }
6876 }
6877 }
6878
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07006879 private void getInterestingRect(Rect r, int line) {
6880 convertFromViewportToContentCoordinates(r);
6881
6882 // Rectangle can can be expanded on first and last line to take
6883 // padding into account.
6884 // TODO Take left/right padding into account too?
6885 if (line == 0) r.top -= getExtendedPaddingTop();
6886 if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
6887 }
6888
6889 private void convertFromViewportToContentCoordinates(Rect r) {
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07006890 final int horizontalOffset = viewportToContentHorizontalOffset();
6891 r.left += horizontalOffset;
6892 r.right += horizontalOffset;
6893
6894 final int verticalOffset = viewportToContentVerticalOffset();
6895 r.top += verticalOffset;
6896 r.bottom += verticalOffset;
6897 }
6898
Gilles Debunned88876a2012-03-16 17:34:04 -07006899 int viewportToContentHorizontalOffset() {
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07006900 return getCompoundPaddingLeft() - mScrollX;
6901 }
6902
Gilles Debunned88876a2012-03-16 17:34:04 -07006903 int viewportToContentVerticalOffset() {
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07006904 int offset = getExtendedPaddingTop() - mScrollY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006905 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07006906 offset += getVerticalOffset(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006907 }
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07006908 return offset;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006909 }
6910
6911 @Override
6912 public void debug(int depth) {
6913 super.debug(depth);
6914
6915 String output = debugIndent(depth);
6916 output += "frame={" + mLeft + ", " + mTop + ", " + mRight
6917 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
6918 + "} ";
6919
6920 if (mText != null) {
6921
6922 output += "mText=\"" + mText + "\" ";
6923 if (mLayout != null) {
6924 output += "mLayout width=" + mLayout.getWidth()
6925 + " height=" + mLayout.getHeight();
6926 }
6927 } else {
6928 output += "mText=NULL";
6929 }
6930 Log.d(VIEW_LOG_TAG, output);
6931 }
6932
6933 /**
6934 * Convenience for {@link Selection#getSelectionStart}.
6935 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006936 @ViewDebug.ExportedProperty(category = "text")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006937 public int getSelectionStart() {
6938 return Selection.getSelectionStart(getText());
6939 }
6940
6941 /**
6942 * Convenience for {@link Selection#getSelectionEnd}.
6943 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006944 @ViewDebug.ExportedProperty(category = "text")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006945 public int getSelectionEnd() {
6946 return Selection.getSelectionEnd(getText());
6947 }
6948
6949 /**
6950 * Return true iff there is a selection inside this text view.
6951 */
6952 public boolean hasSelection() {
Gilles Debunne03789e82010-09-07 19:07:17 -07006953 final int selectionStart = getSelectionStart();
6954 final int selectionEnd = getSelectionEnd();
6955
6956 return selectionStart >= 0 && selectionStart != selectionEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006957 }
6958
6959 /**
6960 * Sets the properties of this field (lines, horizontally scrolling,
6961 * transformation method) to be for a single-line input.
6962 *
6963 * @attr ref android.R.styleable#TextView_singleLine
6964 */
6965 public void setSingleLine() {
6966 setSingleLine(true);
6967 }
6968
6969 /**
Adam Powell7f8f79a2011-07-07 18:35:54 -07006970 * Sets the properties of this field to transform input to ALL CAPS
6971 * display. This may use a "small caps" formatting if available.
6972 * This setting will be ignored if this field is editable or selectable.
6973 *
6974 * This call replaces the current transformation method. Disabling this
6975 * will not necessarily restore the previous behavior from before this
6976 * was enabled.
6977 *
6978 * @see #setTransformationMethod(TransformationMethod)
6979 * @attr ref android.R.styleable#TextView_textAllCaps
6980 */
6981 public void setAllCaps(boolean allCaps) {
6982 if (allCaps) {
6983 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
6984 } else {
6985 setTransformationMethod(null);
6986 }
6987 }
6988
6989 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08006990 * If true, sets the properties of this field (number of lines, horizontally scrolling,
6991 * transformation method) to be for a single-line input; if false, restores these to the default
6992 * conditions.
6993 *
6994 * Note that the default conditions are not necessarily those that were in effect prior this
6995 * method, and you may want to reset these properties to your custom values.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006996 *
6997 * @attr ref android.R.styleable#TextView_singleLine
6998 */
6999 @android.view.RemotableViewMethod
7000 public void setSingleLine(boolean singleLine) {
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08007001 // Could be used, but may break backward compatibility.
7002 // if (mSingleLine == singleLine) return;
Gilles Debunned7483bf2010-11-10 10:47:45 -08007003 setInputTypeSingleLine(singleLine);
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08007004 applySingleLine(singleLine, true, true);
Gilles Debunned7483bf2010-11-10 10:47:45 -08007005 }
7006
7007 /**
7008 * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
7009 * @param singleLine
7010 */
7011 private void setInputTypeSingleLine(boolean singleLine) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007012 if (mEditor != null &&
7013 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007014 if (singleLine) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007015 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007016 } else {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007017 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007018 }
7019 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007020 }
7021
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08007022 private void applySingleLine(boolean singleLine, boolean applyTransformation,
7023 boolean changeMaxLines) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007024 mSingleLine = singleLine;
7025 if (singleLine) {
7026 setLines(1);
7027 setHorizontallyScrolling(true);
7028 if (applyTransformation) {
Gilles Debunne91a08cf2010-11-08 17:34:49 -08007029 setTransformationMethod(SingleLineTransformationMethod.getInstance());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007030 }
7031 } else {
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08007032 if (changeMaxLines) {
7033 setMaxLines(Integer.MAX_VALUE);
7034 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007035 setHorizontallyScrolling(false);
7036 if (applyTransformation) {
7037 setTransformationMethod(null);
7038 }
7039 }
7040 }
Gilles Debunneb2316962010-12-21 17:32:43 -08007041
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007042 /**
7043 * Causes words in the text that are longer than the view is wide
7044 * to be ellipsized instead of broken in the middle. You may also
7045 * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
Kenny Roote855d132009-06-11 11:00:42 -05007046 * to constrain the text to a single line. Use <code>null</code>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007047 * to turn off ellipsizing.
7048 *
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07007049 * If {@link #setMaxLines} has been used to set two or more lines,
Gilles Debunne6435a562011-08-04 21:22:30 -07007050 * {@link android.text.TextUtils.TruncateAt#END} and
7051 * {@link android.text.TextUtils.TruncateAt#MARQUEE}* are only supported
7052 * (other ellipsizing types will not do anything).
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07007053 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007054 * @attr ref android.R.styleable#TextView_ellipsize
7055 */
7056 public void setEllipsize(TextUtils.TruncateAt where) {
Gilles Debunne22378292011-08-12 10:38:52 -07007057 // TruncateAt is an enum. != comparison is ok between these singleton objects.
7058 if (mEllipsize != where) {
7059 mEllipsize = where;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007060
Gilles Debunne22378292011-08-12 10:38:52 -07007061 if (mLayout != null) {
7062 nullLayouts();
7063 requestLayout();
7064 invalidate();
7065 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007066 }
7067 }
7068
7069 /**
7070 * Sets how many times to repeat the marquee animation. Only applied if the
7071 * TextView has marquee enabled. Set to -1 to repeat indefinitely.
7072 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07007073 * @see #getMarqueeRepeatLimit()
7074 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007075 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
7076 */
7077 public void setMarqueeRepeatLimit(int marqueeLimit) {
7078 mMarqueeRepeatLimit = marqueeLimit;
7079 }
7080
7081 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07007082 * Gets the number of times the marquee animation is repeated. Only meaningful if the
7083 * TextView has marquee enabled.
7084 *
7085 * @return the number of times the marquee animation is repeated. -1 if the animation
7086 * repeats indefinitely
7087 *
7088 * @see #setMarqueeRepeatLimit(int)
7089 *
7090 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
7091 */
7092 public int getMarqueeRepeatLimit() {
7093 return mMarqueeRepeatLimit;
7094 }
7095
7096 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007097 * Returns where, if anywhere, words that are longer than the view
7098 * is wide should be ellipsized.
7099 */
7100 @ViewDebug.ExportedProperty
7101 public TextUtils.TruncateAt getEllipsize() {
7102 return mEllipsize;
7103 }
7104
7105 /**
7106 * Set the TextView so that when it takes focus, all the text is
7107 * selected.
7108 *
7109 * @attr ref android.R.styleable#TextView_selectAllOnFocus
7110 */
7111 @android.view.RemotableViewMethod
7112 public void setSelectAllOnFocus(boolean selectAllOnFocus) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07007113 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07007114 mEditor.mSelectAllOnFocus = selectAllOnFocus;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007115
7116 if (selectAllOnFocus && !(mText instanceof Spannable)) {
7117 setText(mText, BufferType.SPANNABLE);
7118 }
7119 }
7120
7121 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07007122 * Set whether the cursor is visible. The default is true. Note that this property only
7123 * makes sense for editable TextView.
7124 *
7125 * @see #isCursorVisible()
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007126 *
7127 * @attr ref android.R.styleable#TextView_cursorVisible
7128 */
7129 @android.view.RemotableViewMethod
7130 public void setCursorVisible(boolean visible) {
Gilles Debunne60e21862012-01-30 15:04:14 -08007131 if (visible && mEditor == null) return; // visible is the default value with no edit data
Gilles Debunne5fae9962012-05-08 14:53:20 -07007132 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07007133 if (mEditor.mCursorVisible != visible) {
7134 mEditor.mCursorVisible = visible;
Gilles Debunne3d010062011-02-18 14:16:41 -08007135 invalidate();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007136
Gilles Debunne2d373a12012-04-20 15:32:19 -07007137 mEditor.makeBlink();
Gilles Debunnef788a9f2010-07-22 10:17:23 -07007138
Gilles Debunne3d010062011-02-18 14:16:41 -08007139 // InsertionPointCursorController depends on mCursorVisible
Gilles Debunne2d373a12012-04-20 15:32:19 -07007140 mEditor.prepareCursorControllers();
Gilles Debunne3d010062011-02-18 14:16:41 -08007141 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007142 }
7143
Gilles Debunnef03acef2012-04-30 19:26:19 -07007144 /**
7145 * @return whether or not the cursor is visible (assuming this TextView is editable)
7146 *
7147 * @see #setCursorVisible(boolean)
7148 *
7149 * @attr ref android.R.styleable#TextView_cursorVisible
7150 */
7151 public boolean isCursorVisible() {
7152 // true is the default value
7153 return mEditor == null ? true : mEditor.mCursorVisible;
7154 }
7155
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007156 private boolean canMarquee() {
7157 int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight());
Adam Powell282e3772011-08-30 16:51:11 -07007158 return width > 0 && (mLayout.getLineWidth(0) > width ||
7159 (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null &&
7160 mSavedMarqueeModeLayout.getLineWidth(0) > width));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007161 }
7162
7163 private void startMarquee() {
Romain Guy4dc4f732009-06-19 15:16:40 -07007164 // Do not ellipsize EditText
Gilles Debunne60e21862012-01-30 15:04:14 -08007165 if (getKeyListener() != null) return;
Romain Guy4dc4f732009-06-19 15:16:40 -07007166
Romain Guy939151f2009-04-08 14:22:40 -07007167 if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
7168 return;
7169 }
7170
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007171 if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) &&
7172 getLineCount() == 1 && canMarquee()) {
Romain Guy939151f2009-04-08 14:22:40 -07007173
Adam Powell282e3772011-08-30 16:51:11 -07007174 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
7175 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
7176 final Layout tmp = mLayout;
7177 mLayout = mSavedMarqueeModeLayout;
7178 mSavedMarqueeModeLayout = tmp;
7179 setHorizontalFadingEdgeEnabled(true);
7180 requestLayout();
7181 invalidate();
7182 }
7183
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007184 if (mMarquee == null) mMarquee = new Marquee(this);
7185 mMarquee.start(mMarqueeRepeatLimit);
7186 }
7187 }
7188
7189 private void stopMarquee() {
7190 if (mMarquee != null && !mMarquee.isStopped()) {
7191 mMarquee.stop();
7192 }
Adam Powell282e3772011-08-30 16:51:11 -07007193
7194 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
7195 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
7196 final Layout tmp = mSavedMarqueeModeLayout;
7197 mSavedMarqueeModeLayout = mLayout;
7198 mLayout = tmp;
7199 setHorizontalFadingEdgeEnabled(false);
7200 requestLayout();
7201 invalidate();
7202 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007203 }
7204
7205 private void startStopMarquee(boolean start) {
7206 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7207 if (start) {
7208 startMarquee();
7209 } else {
7210 stopMarquee();
7211 }
7212 }
7213 }
7214
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007215 /**
Gilles Debunne4469e602011-03-09 14:38:04 -08007216 * This method is called when the text is changed, in case any subclasses
7217 * would like to know.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007218 *
Gilles Debunne4469e602011-03-09 14:38:04 -08007219 * Within <code>text</code>, the <code>lengthAfter</code> characters
7220 * beginning at <code>start</code> have just replaced old text that had
7221 * length <code>lengthBefore</code>. It is an error to attempt to make
7222 * changes to <code>text</code> from this callback.
7223 *
7224 * @param text The text the TextView is displaying
7225 * @param start The offset of the start of the range of the text that was
7226 * modified
7227 * @param lengthBefore The length of the former text that has been replaced
7228 * @param lengthAfter The length of the replacement modified text
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007229 */
Gilles Debunne4469e602011-03-09 14:38:04 -08007230 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
Gilles Debunne6435a562011-08-04 21:22:30 -07007231 // intentionally empty, template pattern method can be overridden by subclasses
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007232 }
7233
7234 /**
7235 * This method is called when the selection has changed, in case any
7236 * subclasses would like to know.
Gilles Debunne2d373a12012-04-20 15:32:19 -07007237 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007238 * @param selStart The new selection start location.
7239 * @param selEnd The new selection end location.
7240 */
7241 protected void onSelectionChanged(int selStart, int selEnd) {
Svetoslav Ganova0156172011-06-26 17:55:44 -07007242 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
Svetoslavbcc46a02013-02-06 11:56:00 -08007243 notifyAccessibilityStateChanged();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007244 }
Svetoslav Ganova0156172011-06-26 17:55:44 -07007245
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007246 /**
7247 * Adds a TextWatcher to the list of those whose methods are called
7248 * whenever this TextView's text changes.
7249 * <p>
7250 * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
7251 * not called after {@link #setText} calls. Now, doing {@link #setText}
7252 * if there are any text changed listeners forces the buffer type to
7253 * Editable if it would not otherwise be and does call this method.
7254 */
7255 public void addTextChangedListener(TextWatcher watcher) {
7256 if (mListeners == null) {
7257 mListeners = new ArrayList<TextWatcher>();
7258 }
7259
7260 mListeners.add(watcher);
7261 }
7262
7263 /**
7264 * Removes the specified TextWatcher from the list of those whose
7265 * methods are called
7266 * whenever this TextView's text changes.
7267 */
7268 public void removeTextChangedListener(TextWatcher watcher) {
7269 if (mListeners != null) {
7270 int i = mListeners.indexOf(watcher);
7271
7272 if (i >= 0) {
7273 mListeners.remove(i);
7274 }
7275 }
7276 }
7277
Gilles Debunne6435a562011-08-04 21:22:30 -07007278 private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007279 if (mListeners != null) {
7280 final ArrayList<TextWatcher> list = mListeners;
7281 final int count = list.size();
7282 for (int i = 0; i < count; i++) {
7283 list.get(i).beforeTextChanged(text, start, before, after);
7284 }
7285 }
Gilles Debunne6435a562011-08-04 21:22:30 -07007286
7287 // The spans that are inside or intersect the modified region no longer make sense
7288 removeIntersectingSpans(start, start + before, SpellCheckSpan.class);
7289 removeIntersectingSpans(start, start + before, SuggestionSpan.class);
7290 }
7291
7292 // Removes all spans that are inside or actually overlap the start..end range
7293 private <T> void removeIntersectingSpans(int start, int end, Class<T> type) {
7294 if (!(mText instanceof Editable)) return;
7295 Editable text = (Editable) mText;
7296
7297 T[] spans = text.getSpans(start, end, type);
7298 final int length = spans.length;
7299 for (int i = 0; i < length; i++) {
7300 final int s = text.getSpanStart(spans[i]);
7301 final int e = text.getSpanEnd(spans[i]);
Gilles Debunneb062e812011-09-27 14:58:37 -07007302 // Spans that are adjacent to the edited region will be handled in
7303 // updateSpellCheckSpans. Result depends on what will be added (space or text)
Gilles Debunne6435a562011-08-04 21:22:30 -07007304 if (e == start || s == end) break;
7305 text.removeSpan(spans[i]);
7306 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007307 }
7308
7309 /**
7310 * Not private so it can be called from an inner class without going
7311 * through a thunk.
7312 */
Gilles Debunne6435a562011-08-04 21:22:30 -07007313 void sendOnTextChanged(CharSequence text, int start, int before, int after) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007314 if (mListeners != null) {
7315 final ArrayList<TextWatcher> list = mListeners;
7316 final int count = list.size();
7317 for (int i = 0; i < count; i++) {
7318 list.get(i).onTextChanged(text, start, before, after);
7319 }
7320 }
Gilles Debunne1a22db22011-11-20 22:13:21 +01007321
Gilles Debunne2d373a12012-04-20 15:32:19 -07007322 if (mEditor != null) mEditor.sendOnTextChanged(start, after);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007323 }
7324
7325 /**
7326 * Not private so it can be called from an inner class without going
7327 * through a thunk.
7328 */
7329 void sendAfterTextChanged(Editable text) {
7330 if (mListeners != null) {
7331 final ArrayList<TextWatcher> list = mListeners;
7332 final int count = list.size();
7333 for (int i = 0; i < count; i++) {
7334 list.get(i).afterTextChanged(text);
7335 }
7336 }
7337 }
7338
Gilles Debunned88876a2012-03-16 17:34:04 -07007339 void updateAfterEdit() {
7340 invalidate();
7341 int curs = getSelectionStart();
7342
7343 if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
7344 registerForPreDraw();
7345 }
7346
Raph Levienf5c1a872012-10-15 17:22:26 -07007347 checkForResize();
7348
Gilles Debunned88876a2012-03-16 17:34:04 -07007349 if (curs >= 0) {
7350 mHighlightPathBogus = true;
Gilles Debunne2d373a12012-04-20 15:32:19 -07007351 if (mEditor != null) mEditor.makeBlink();
Gilles Debunned88876a2012-03-16 17:34:04 -07007352 bringPointIntoView(curs);
7353 }
Gilles Debunned88876a2012-03-16 17:34:04 -07007354 }
7355
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007356 /**
7357 * Not private so it can be called from an inner class without going
7358 * through a thunk.
7359 */
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08007360 void handleTextChanged(CharSequence buffer, int start, int before, int after) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007361 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007362 if (ims == null || ims.mBatchEditNesting == 0) {
7363 updateAfterEdit();
7364 }
7365 if (ims != null) {
7366 ims.mContentChanged = true;
7367 if (ims.mChangedStart < 0) {
7368 ims.mChangedStart = start;
7369 ims.mChangedEnd = start+before;
7370 } else {
Viktor Yakovel964be412010-02-17 08:35:57 +01007371 ims.mChangedStart = Math.min(ims.mChangedStart, start);
7372 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007373 }
7374 ims.mChangedDelta += after-before;
7375 }
Gilles Debunne186aaf92011-09-16 14:26:12 -07007376
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007377 sendOnTextChanged(buffer, start, before, after);
7378 onTextChanged(buffer, start, before, after);
7379 }
Gilles Debunne60e21862012-01-30 15:04:14 -08007380
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007381 /**
7382 * Not private so it can be called from an inner class without going
7383 * through a thunk.
7384 */
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08007385 void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007386 // XXX Make the start and end move together if this ends up
7387 // spending too much time invalidating.
7388
7389 boolean selChanged = false;
7390 int newSelStart=-1, newSelEnd=-1;
Gilles Debunne60e21862012-01-30 15:04:14 -08007391
Gilles Debunne2d373a12012-04-20 15:32:19 -07007392 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
Gilles Debunne60e21862012-01-30 15:04:14 -08007393
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007394 if (what == Selection.SELECTION_END) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007395 selChanged = true;
7396 newSelEnd = newStart;
7397
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007398 if (oldStart >= 0 || newStart >= 0) {
7399 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
Raph Levienf5c1a872012-10-15 17:22:26 -07007400 checkForResize();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007401 registerForPreDraw();
Gilles Debunne2d373a12012-04-20 15:32:19 -07007402 if (mEditor != null) mEditor.makeBlink();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007403 }
7404 }
7405
7406 if (what == Selection.SELECTION_START) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007407 selChanged = true;
7408 newSelStart = newStart;
7409
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007410 if (oldStart >= 0 || newStart >= 0) {
7411 int end = Selection.getSelectionEnd(buf);
7412 invalidateCursor(end, oldStart, newStart);
7413 }
7414 }
7415
7416 if (selChanged) {
Gilles Debunne83051b82012-02-24 20:01:13 -08007417 mHighlightPathBogus = true;
Gilles Debunne2d373a12012-04-20 15:32:19 -07007418 if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
Gilles Debunne60e21862012-01-30 15:04:14 -08007419
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007420 if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) {
7421 if (newSelStart < 0) {
7422 newSelStart = Selection.getSelectionStart(buf);
7423 }
7424 if (newSelEnd < 0) {
7425 newSelEnd = Selection.getSelectionEnd(buf);
7426 }
7427 onSelectionChanged(newSelStart, newSelEnd);
7428 }
7429 }
Gilles Debunne8615ac92011-11-29 15:25:03 -08007430
Gilles Debunneb35ab7b2011-12-05 15:54:00 -08007431 if (what instanceof UpdateAppearance || what instanceof ParagraphStyle ||
7432 what instanceof CharacterStyle) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007433 if (ims == null || ims.mBatchEditNesting == 0) {
7434 invalidate();
Gilles Debunne83051b82012-02-24 20:01:13 -08007435 mHighlightPathBogus = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007436 checkForResize();
7437 } else {
7438 ims.mContentChanged = true;
7439 }
Gilles Debunneebc86af2012-04-20 15:10:47 -07007440 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007441 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
7442 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
Gilles Debunneebc86af2012-04-20 15:10:47 -07007443 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007444 }
7445
7446 if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
Gilles Debunne83051b82012-02-24 20:01:13 -08007447 mHighlightPathBogus = true;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07007448 if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
7449 ims.mSelectionModeChanged = true;
7450 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007451
7452 if (Selection.getSelectionStart(buf) >= 0) {
7453 if (ims == null || ims.mBatchEditNesting == 0) {
7454 invalidateCursor();
7455 } else {
7456 ims.mCursorChanged = true;
7457 }
7458 }
7459 }
Gilles Debunne6435a562011-08-04 21:22:30 -07007460
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007461 if (what instanceof ParcelableSpan) {
7462 // If this is a span that can be sent to a remote process,
7463 // the current extract editor would be interested in it.
Gilles Debunnec62589c2012-04-12 14:50:23 -07007464 if (ims != null && ims.mExtractedTextRequest != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007465 if (ims.mBatchEditNesting != 0) {
7466 if (oldStart >= 0) {
7467 if (ims.mChangedStart > oldStart) {
7468 ims.mChangedStart = oldStart;
7469 }
7470 if (ims.mChangedStart > oldEnd) {
7471 ims.mChangedStart = oldEnd;
7472 }
7473 }
7474 if (newStart >= 0) {
7475 if (ims.mChangedStart > newStart) {
7476 ims.mChangedStart = newStart;
7477 }
7478 if (ims.mChangedStart > newEnd) {
7479 ims.mChangedStart = newEnd;
7480 }
7481 }
7482 } else {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007483 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: "
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007484 + oldStart + "-" + oldEnd + ","
Gilles Debunnec62589c2012-04-12 14:50:23 -07007485 + newStart + "-" + newEnd + " " + what);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007486 ims.mContentChanged = true;
7487 }
7488 }
7489 }
Gilles Debunne6435a562011-08-04 21:22:30 -07007490
Gilles Debunne2d373a12012-04-20 15:32:19 -07007491 if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0 &&
7492 what instanceof SpellCheckSpan) {
Gilles Debunne69865bd2012-05-09 11:12:03 -07007493 mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
Gilles Debunne6435a562011-08-04 21:22:30 -07007494 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007495 }
7496
Gilles Debunne6435a562011-08-04 21:22:30 -07007497 /**
Romain Guydcc490f2010-02-24 17:59:35 -08007498 * @hide
7499 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007500 @Override
Romain Guya440b002010-02-24 15:57:54 -08007501 public void dispatchFinishTemporaryDetach() {
7502 mDispatchTemporaryDetach = true;
7503 super.dispatchFinishTemporaryDetach();
7504 mDispatchTemporaryDetach = false;
7505 }
7506
7507 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007508 public void onStartTemporaryDetach() {
Romain Guya440b002010-02-24 15:57:54 -08007509 super.onStartTemporaryDetach();
7510 // Only track when onStartTemporaryDetach() is called directly,
7511 // usually because this instance is an editable field in a list
7512 if (!mDispatchTemporaryDetach) mTemporaryDetach = true;
Gilles Debunne4b2274f2011-02-25 15:18:03 -08007513
Adam Powell057a5852012-05-11 10:28:38 -07007514 // Tell the editor that we are temporarily detached. It can use this to preserve
7515 // selection state as needed.
7516 if (mEditor != null) mEditor.mTemporaryDetach = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007517 }
Gilles Debunne3784a7f2011-07-15 13:49:38 -07007518
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007519 @Override
7520 public void onFinishTemporaryDetach() {
Romain Guya440b002010-02-24 15:57:54 -08007521 super.onFinishTemporaryDetach();
7522 // Only track when onStartTemporaryDetach() is called directly,
7523 // usually because this instance is an editable field in a list
7524 if (!mDispatchTemporaryDetach) mTemporaryDetach = false;
Adam Powell057a5852012-05-11 10:28:38 -07007525 if (mEditor != null) mEditor.mTemporaryDetach = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007526 }
Gilles Debunne3784a7f2011-07-15 13:49:38 -07007527
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007528 @Override
7529 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
7530 if (mTemporaryDetach) {
7531 // If we are temporarily in the detach state, then do nothing.
7532 super.onFocusChanged(focused, direction, previouslyFocusedRect);
7533 return;
7534 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007535
Gilles Debunne2d373a12012-04-20 15:32:19 -07007536 if (mEditor != null) mEditor.onFocusChanged(focused, direction);
Gilles Debunne03789e82010-09-07 19:07:17 -07007537
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007538 if (focused) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007539 if (mText instanceof Spannable) {
7540 Spannable sp = (Spannable) mText;
7541 MetaKeyKeyListener.resetMetaState(sp);
7542 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007543 }
7544
7545 startStopMarquee(focused);
7546
7547 if (mTransformation != null) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07007548 mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007549 }
7550
7551 super.onFocusChanged(focused, direction, previouslyFocusedRect);
7552 }
7553
7554 @Override
7555 public void onWindowFocusChanged(boolean hasWindowFocus) {
7556 super.onWindowFocusChanged(hasWindowFocus);
7557
Gilles Debunne2d373a12012-04-20 15:32:19 -07007558 if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007559
7560 startStopMarquee(hasWindowFocus);
7561 }
7562
Adam Powellba0a2c32010-09-28 17:41:23 -07007563 @Override
7564 protected void onVisibilityChanged(View changedView, int visibility) {
7565 super.onVisibilityChanged(changedView, visibility);
Gilles Debunne60e21862012-01-30 15:04:14 -08007566 if (mEditor != null && visibility != VISIBLE) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007567 mEditor.hideControllers();
Adam Powellba0a2c32010-09-28 17:41:23 -07007568 }
7569 }
7570
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007571 /**
7572 * Use {@link BaseInputConnection#removeComposingSpans
7573 * BaseInputConnection.removeComposingSpans()} to remove any IME composing
7574 * state from this text view.
7575 */
7576 public void clearComposingText() {
7577 if (mText instanceof Spannable) {
7578 BaseInputConnection.removeComposingSpans((Spannable)mText);
7579 }
7580 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07007581
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007582 @Override
7583 public void setSelected(boolean selected) {
7584 boolean wasSelected = isSelected();
7585
7586 super.setSelected(selected);
7587
7588 if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7589 if (selected) {
7590 startMarquee();
7591 } else {
7592 stopMarquee();
7593 }
7594 }
7595 }
7596
7597 @Override
7598 public boolean onTouchEvent(MotionEvent event) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07007599 final int action = event.getActionMasked();
Adam Powell965b9692010-10-21 18:44:32 -07007600
Gilles Debunne2d373a12012-04-20 15:32:19 -07007601 if (mEditor != null) mEditor.onTouchEvent(event);
Gilles Debunnebbb5d6e2010-06-21 16:21:51 -07007602
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007603 final boolean superResult = super.onTouchEvent(event);
7604
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007605 /*
7606 * Don't handle the release after a long press, because it will
7607 * move the selection away from whatever the menu action was
7608 * trying to affect.
7609 */
Gilles Debunne2d373a12012-04-20 15:32:19 -07007610 if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
7611 mEditor.mDiscardNextActionUp = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007612 return superResult;
7613 }
7614
Gilles Debunne70a63122011-09-01 13:27:33 -07007615 final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) &&
Gilles Debunne2d373a12012-04-20 15:32:19 -07007616 (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
Gilles Debunnec3e85a72011-01-21 08:46:06 -08007617
Gilles Debunne70a63122011-09-01 13:27:33 -07007618 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
Janos Levai042856c2010-10-15 02:53:58 +03007619 && mText instanceof Spannable && mLayout != null) {
Gilles Debunnef788a9f2010-07-22 10:17:23 -07007620 boolean handled = false;
7621
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07007622 if (mMovement != null) {
7623 handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
7624 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007625
Gilles Debunne60e21862012-01-30 15:04:14 -08007626 final boolean textIsSelectable = isTextSelectable();
7627 if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
Gilles Debunnef3895ed2010-12-21 12:53:58 -08007628 // The LinkMovementMethod which should handle taps on links has not been installed
Gilles Debunne70a63122011-09-01 13:27:33 -07007629 // on non editable text that support text selection.
7630 // We reproduce its behavior here to open links for these.
Gilles Debunnef3895ed2010-12-21 12:53:58 -08007631 ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
7632 getSelectionEnd(), ClickableSpan.class);
7633
Gilles Debunne822b8f02012-01-17 18:02:15 -08007634 if (links.length > 0) {
Gilles Debunnef3895ed2010-12-21 12:53:58 -08007635 links[0].onClick(this);
7636 handled = true;
7637 }
7638 }
7639
Gilles Debunne60e21862012-01-30 15:04:14 -08007640 if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
Gilles Debunne180bb1b2011-03-10 11:14:00 -08007641 // Show the IME, except when selecting in read-only text.
satok863fcd62011-06-21 17:38:02 +09007642 final InputMethodManager imm = InputMethodManager.peekInstance();
satoka67a3cf2011-09-07 17:14:03 +09007643 viewClicked(imm);
Gilles Debunne3473b2b2012-04-20 16:21:10 -07007644 if (!textIsSelectable && mEditor.mShowSoftInputOnFocus) {
Gilles Debunne180bb1b2011-03-10 11:14:00 -08007645 handled |= imm != null && imm.showSoftInput(this, 0);
Adam Powell879fb6b2010-09-20 11:23:56 -07007646 }
Gilles Debunnebbb5d6e2010-06-21 16:21:51 -07007647
Gilles Debunned88876a2012-03-16 17:34:04 -07007648 // The above condition ensures that the mEditor is not null
Gilles Debunne2d373a12012-04-20 15:32:19 -07007649 mEditor.onTouchUpEvent(event);
Gilles Debunne6435a562011-08-04 21:22:30 -07007650
7651 handled = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007652 }
7653
The Android Open Source Project4df24232009-03-05 14:34:35 -08007654 if (handled) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007655 return true;
7656 }
7657 }
7658
7659 return superResult;
7660 }
7661
Jeff Brown8f345672011-02-26 13:29:53 -08007662 @Override
7663 public boolean onGenericMotionEvent(MotionEvent event) {
7664 if (mMovement != null && mText instanceof Spannable && mLayout != null) {
7665 try {
7666 if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) {
7667 return true;
7668 }
7669 } catch (AbstractMethodError ex) {
7670 // onGenericMotionEvent was added to the MovementMethod interface in API 12.
7671 // Ignore its absence in case third party applications implemented the
7672 // interface directly.
7673 }
7674 }
7675 return super.onGenericMotionEvent(event);
7676 }
7677
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007678 /**
Gilles Debunne86b9c782010-11-11 10:43:48 -08007679 * @return True iff this TextView contains a text that can be edited, or if this is
7680 * a selectable TextView.
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007681 */
Gilles Debunned88876a2012-03-16 17:34:04 -07007682 boolean isTextEditable() {
Gilles Debunnef076eeb2010-11-29 11:32:53 -08007683 return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007684 }
7685
The Android Open Source Project4df24232009-03-05 14:34:35 -08007686 /**
7687 * Returns true, only while processing a touch gesture, if the initial
7688 * 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 -07007689 * its selection changed. Only valid while processing the touch gesture
Gilles Debunne053c4392012-03-15 15:35:26 -07007690 * of interest, in an editable text view.
The Android Open Source Project4df24232009-03-05 14:34:35 -08007691 */
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07007692 public boolean didTouchFocusSelect() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007693 return mEditor != null && mEditor.mTouchFocusSelected;
The Android Open Source Project4df24232009-03-05 14:34:35 -08007694 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07007695
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007696 @Override
7697 public void cancelLongPress() {
7698 super.cancelLongPress();
Gilles Debunne2d373a12012-04-20 15:32:19 -07007699 if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007700 }
Gilles Debunne70a63122011-09-01 13:27:33 -07007701
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007702 @Override
7703 public boolean onTrackballEvent(MotionEvent event) {
Gilles Debunne60e21862012-01-30 15:04:14 -08007704 if (mMovement != null && mText instanceof Spannable && mLayout != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007705 if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
7706 return true;
7707 }
7708 }
7709
7710 return super.onTrackballEvent(event);
7711 }
7712
7713 public void setScroller(Scroller s) {
7714 mScroller = s;
7715 }
7716
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007717 @Override
7718 protected float getLeftFadingEdgeStrength() {
Adam Powell282e3772011-08-30 16:51:11 -07007719 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
7720 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007721 if (mMarquee != null && !mMarquee.isStopped()) {
7722 final Marquee marquee = mMarquee;
Romain Guyc2303192009-04-03 17:37:18 -07007723 if (marquee.shouldDrawLeftFade()) {
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07007724 final float scroll = marquee.getScroll();
7725 return scroll / getHorizontalFadingEdgeLength();
Romain Guyc2303192009-04-03 17:37:18 -07007726 } else {
7727 return 0.0f;
7728 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007729 } else if (getLineCount() == 1) {
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07007730 final int layoutDirection = getLayoutDirection();
Fabrice Di Meglioc0053222011-06-13 12:16:51 -07007731 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07007732 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007733 case Gravity.LEFT:
7734 return 0.0f;
7735 case Gravity.RIGHT:
7736 return (mLayout.getLineRight(0) - (mRight - mLeft) -
7737 getCompoundPaddingLeft() - getCompoundPaddingRight() -
7738 mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
7739 case Gravity.CENTER_HORIZONTAL:
7740 return 0.0f;
7741 }
7742 }
7743 }
7744 return super.getLeftFadingEdgeStrength();
7745 }
7746
7747 @Override
7748 protected float getRightFadingEdgeStrength() {
Adam Powell282e3772011-08-30 16:51:11 -07007749 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
7750 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007751 if (mMarquee != null && !mMarquee.isStopped()) {
7752 final Marquee marquee = mMarquee;
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07007753 final float maxFadeScroll = marquee.getMaxFadeScroll();
7754 final float scroll = marquee.getScroll();
7755 return (maxFadeScroll - scroll) / getHorizontalFadingEdgeLength();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007756 } else if (getLineCount() == 1) {
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07007757 final int layoutDirection = getLayoutDirection();
Fabrice Di Meglioc0053222011-06-13 12:16:51 -07007758 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07007759 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007760 case Gravity.LEFT:
Romain Guy076dc9f2009-06-24 17:17:51 -07007761 final int textWidth = (mRight - mLeft) - getCompoundPaddingLeft() -
7762 getCompoundPaddingRight();
7763 final float lineWidth = mLayout.getLineWidth(0);
7764 return (lineWidth - textWidth) / getHorizontalFadingEdgeLength();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007765 case Gravity.RIGHT:
7766 return 0.0f;
7767 case Gravity.CENTER_HORIZONTAL:
Gilles Debunne44c14732010-10-19 11:56:59 -07007768 case Gravity.FILL_HORIZONTAL:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007769 return (mLayout.getLineWidth(0) - ((mRight - mLeft) -
7770 getCompoundPaddingLeft() - getCompoundPaddingRight())) /
7771 getHorizontalFadingEdgeLength();
7772 }
7773 }
7774 }
7775 return super.getRightFadingEdgeStrength();
7776 }
7777
7778 @Override
7779 protected int computeHorizontalScrollRange() {
Romain Guydac5f9f2010-07-08 11:40:54 -07007780 if (mLayout != null) {
7781 return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT ?
7782 (int) mLayout.getLineWidth(0) : mLayout.getWidth();
7783 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007784
7785 return super.computeHorizontalScrollRange();
7786 }
7787
7788 @Override
7789 protected int computeVerticalScrollRange() {
7790 if (mLayout != null)
7791 return mLayout.getHeight();
7792
7793 return super.computeVerticalScrollRange();
7794 }
7795
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07007796 @Override
7797 protected int computeVerticalScrollExtent() {
7798 return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
7799 }
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07007800
7801 @Override
Svetoslav Ganovea515ae2011-09-14 18:15:32 -07007802 public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
7803 super.findViewsWithText(outViews, searched, flags);
7804 if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
7805 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
7806 String searchedLowerCase = searched.toString().toLowerCase();
7807 String textLowerCase = mText.toString().toLowerCase();
7808 if (textLowerCase.contains(searchedLowerCase)) {
7809 outViews.add(this);
7810 }
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07007811 }
7812 }
7813
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007814 public enum BufferType {
7815 NORMAL, SPANNABLE, EDITABLE,
7816 }
7817
7818 /**
7819 * Returns the TextView_textColor attribute from the
John Spurlock330dd532012-12-18 12:03:11 -05007820 * TypedArray, if set, or the TextAppearance_textColor
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007821 * from the TextView_textAppearance attribute, if TextView_textColor
7822 * was not set directly.
7823 */
7824 public static ColorStateList getTextColors(Context context, TypedArray attrs) {
7825 ColorStateList colors;
7826 colors = attrs.getColorStateList(com.android.internal.R.styleable.
7827 TextView_textColor);
7828
7829 if (colors == null) {
7830 int ap = attrs.getResourceId(com.android.internal.R.styleable.
7831 TextView_textAppearance, -1);
7832 if (ap != -1) {
7833 TypedArray appearance;
7834 appearance = context.obtainStyledAttributes(ap,
7835 com.android.internal.R.styleable.TextAppearance);
7836 colors = appearance.getColorStateList(com.android.internal.R.styleable.
7837 TextAppearance_textColor);
7838 appearance.recycle();
7839 }
7840 }
7841
7842 return colors;
7843 }
7844
7845 /**
7846 * Returns the default color from the TextView_textColor attribute
7847 * from the AttributeSet, if set, or the default color from the
7848 * TextAppearance_textColor from the TextView_textAppearance attribute,
7849 * if TextView_textColor was not set directly.
7850 */
7851 public static int getTextColor(Context context,
7852 TypedArray attrs,
7853 int def) {
7854 ColorStateList colors = getTextColors(context, attrs);
7855
7856 if (colors == null) {
7857 return def;
7858 } else {
7859 return colors.getDefaultColor();
7860 }
7861 }
7862
7863 @Override
7864 public boolean onKeyShortcut(int keyCode, KeyEvent event) {
Jeff Brownc1df9072010-12-21 16:38:50 -08007865 final int filteredMetaState = event.getMetaState() & ~KeyEvent.META_CTRL_MASK;
7866 if (KeyEvent.metaStateHasNoModifiers(filteredMetaState)) {
7867 switch (keyCode) {
7868 case KeyEvent.KEYCODE_A:
7869 if (canSelectText()) {
7870 return onTextContextMenuItem(ID_SELECT_ALL);
7871 }
7872 break;
7873 case KeyEvent.KEYCODE_X:
7874 if (canCut()) {
7875 return onTextContextMenuItem(ID_CUT);
7876 }
7877 break;
7878 case KeyEvent.KEYCODE_C:
7879 if (canCopy()) {
7880 return onTextContextMenuItem(ID_COPY);
7881 }
7882 break;
7883 case KeyEvent.KEYCODE_V:
7884 if (canPaste()) {
7885 return onTextContextMenuItem(ID_PASTE);
7886 }
7887 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007888 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007889 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007890 return super.onKeyShortcut(keyCode, event);
7891 }
7892
Gilles Debunnecbcb3452010-12-17 15:31:02 -08007893 /**
7894 * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
7895 * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
Gilles Debunne2d373a12012-04-20 15:32:19 -07007896 * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
7897 * sufficient.
Gilles Debunnecbcb3452010-12-17 15:31:02 -08007898 */
Gilles Debunnebaaace52010-10-01 15:47:13 -07007899 private boolean canSelectText() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007900 return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007901 }
7902
Gilles Debunnecbcb3452010-12-17 15:31:02 -08007903 /**
7904 * Test based on the <i>intrinsic</i> charateristics of the TextView.
7905 * The text must be spannable and the movement method must allow for arbitary selection.
Gilles Debunne2d373a12012-04-20 15:32:19 -07007906 *
Gilles Debunnecbcb3452010-12-17 15:31:02 -08007907 * See also {@link #canSelectText()}.
7908 */
Gilles Debunned88876a2012-03-16 17:34:04 -07007909 boolean textCanBeSelected() {
Gilles Debunne05336272010-07-09 20:13:45 -07007910 // prepareCursorController() relies on this method.
7911 // If you change this condition, make sure prepareCursorController is called anywhere
7912 // the value of this condition might be changed.
Gilles Debunnebb588da2011-07-11 18:26:19 -07007913 if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07007914 return isTextEditable() ||
7915 (isTextSelectable() && mText instanceof Spannable && isEnabled());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007916 }
7917
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09007918 private Locale getTextServicesLocale(boolean allowNullLocale) {
7919 // Start fetching the text services locale asynchronously.
7920 updateTextServicesLocaleAsync();
7921 // If !allowNullLocale and there is no cached text services locale, just return the default
7922 // locale.
7923 return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
7924 : mCurrentSpellCheckerLocaleCache;
7925 }
7926
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07007927 /**
7928 * This is a temporary method. Future versions may support multi-locale text.
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09007929 * Caveat: This method may not return the latest text services locale, but this should be
7930 * acceptable and it's more important to make this method asynchronous.
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07007931 *
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09007932 * @return The locale that should be used for a word iterator
satok05f24702011-11-02 19:29:35 +09007933 * in this TextView, based on the current spell checker settings,
7934 * the current IME's locale, or the system default locale.
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09007935 * Please note that a word iterator in this TextView is different from another word iterator
7936 * used by SpellChecker.java of TextView. This method should be used for the former.
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07007937 * @hide
7938 */
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09007939 // TODO: Support multi-locale
7940 // TODO: Update the text services locale immediately after the keyboard locale is switched
7941 // by catching intent of keyboard switch event
satok05f24702011-11-02 19:29:35 +09007942 public Locale getTextServicesLocale() {
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09007943 return getTextServicesLocale(false /* allowNullLocale */);
7944 }
7945
7946 /**
7947 * This is a temporary method. Future versions may support multi-locale text.
7948 * Caveat: This method may not return the latest spell checker locale, but this should be
7949 * acceptable and it's more important to make this method asynchronous.
7950 *
7951 * @return The locale that should be used for a spell checker in this TextView,
7952 * based on the current spell checker settings, the current IME's locale, or the system default
7953 * locale.
7954 * @hide
7955 */
7956 public Locale getSpellCheckerLocale() {
7957 return getTextServicesLocale(true /* allowNullLocale */);
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09007958 }
7959
7960 private void updateTextServicesLocaleAsync() {
7961 AsyncTask.execute(new Runnable() {
7962 @Override
7963 public void run() {
7964 if (mCurrentTextServicesLocaleLock.tryLock()) {
7965 try {
7966 updateTextServicesLocaleLocked();
7967 } finally {
7968 mCurrentTextServicesLocaleLock.unlock();
7969 }
7970 }
7971 }
7972 });
7973 }
7974
7975 private void updateTextServicesLocaleLocked() {
satok05f24702011-11-02 19:29:35 +09007976 final TextServicesManager textServicesManager = (TextServicesManager)
7977 mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
7978 final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09007979 final Locale locale;
satok05f24702011-11-02 19:29:35 +09007980 if (subtype != null) {
satokf927e172012-05-24 16:52:54 +09007981 locale = SpellCheckerSubtype.constructLocaleFromString(subtype.getLocale());
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09007982 } else {
7983 locale = null;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07007984 }
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09007985 mCurrentSpellCheckerLocaleCache = locale;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07007986 }
7987
7988 void onLocaleChanged() {
7989 // Will be re-created on demand in getWordIterator with the proper new locale
Gilles Debunne2d373a12012-04-20 15:32:19 -07007990 mEditor.mWordIterator = null;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07007991 }
7992
7993 /**
Gilles Debunned88876a2012-03-16 17:34:04 -07007994 * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
7995 * Made available to achieve a consistent behavior.
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07007996 * @hide
7997 */
7998 public WordIterator getWordIterator() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007999 if (mEditor != null) {
8000 return mEditor.getWordIterator();
Gilles Debunned88876a2012-03-16 17:34:04 -07008001 } else {
8002 return null;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008003 }
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07008004 }
Gilles Debunnedf4ee432010-08-25 19:13:48 -07008005
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008006 @Override
Svetoslav Ganov736c2752011-04-22 18:30:36 -07008007 public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
Svetoslav Ganov887e1a12011-04-29 15:09:28 -07008008 super.onPopulateAccessibilityEvent(event);
8009
Svetoslav Ganov1d1e1102010-11-16 16:44:03 -08008010 final boolean isPassword = hasPasswordTransformationMethod();
alanv7d624192012-05-21 14:23:17 -07008011 if (!isPassword || shouldSpeakPasswordsForAccessibility()) {
8012 final CharSequence text = getTextForAccessibility();
Svetoslav Ganovd37848a2011-09-20 14:03:55 -07008013 if (!TextUtils.isEmpty(text)) {
svetoslavganov75986cf2009-05-14 22:28:01 -07008014 event.getText().add(text);
8015 }
svetoslavganov75986cf2009-05-14 22:28:01 -07008016 }
svetoslavganov75986cf2009-05-14 22:28:01 -07008017 }
8018
alanv7d624192012-05-21 14:23:17 -07008019 /**
8020 * @return true if the user has explicitly allowed accessibility services
8021 * to speak passwords.
8022 */
8023 private boolean shouldSpeakPasswordsForAccessibility() {
8024 return (Settings.Secure.getInt(mContext.getContentResolver(),
8025 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0) == 1);
8026 }
8027
Svetoslav Ganov30401322011-05-12 18:53:45 -07008028 @Override
8029 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
8030 super.onInitializeAccessibilityEvent(event);
8031
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08008032 event.setClassName(TextView.class.getName());
Svetoslav Ganov30401322011-05-12 18:53:45 -07008033 final boolean isPassword = hasPasswordTransformationMethod();
8034 event.setPassword(isPassword);
Svetoslav Ganova0156172011-06-26 17:55:44 -07008035
8036 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
8037 event.setFromIndex(Selection.getSelectionStart(mText));
8038 event.setToIndex(Selection.getSelectionEnd(mText));
8039 event.setItemCount(mText.length());
8040 }
Svetoslav Ganov30401322011-05-12 18:53:45 -07008041 }
8042
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07008043 @Override
8044 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
8045 super.onInitializeAccessibilityNodeInfo(info);
8046
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08008047 info.setClassName(TextView.class.getName());
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07008048 final boolean isPassword = hasPasswordTransformationMethod();
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08008049 info.setPassword(isPassword);
8050
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07008051 if (!isPassword) {
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07008052 info.setText(getTextForAccessibility());
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07008053 }
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008054
Svetoslavbcc46a02013-02-06 11:56:00 -08008055 if (mBufferType == BufferType.EDITABLE) {
8056 info.setEditable(true);
8057 }
8058
Svetoslavdb7da0e2013-04-22 18:34:02 -07008059 if (!TextUtils.isEmpty(mText)) {
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008060 info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
8061 info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
8062 info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
8063 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
8064 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
8065 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
8066 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
8067 }
Svetoslavdb7da0e2013-04-22 18:34:02 -07008068
Svetoslav7c512842013-01-30 23:02:08 -08008069 if (isFocused()) {
8070 if (canSelectText()) {
8071 info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
8072 }
8073 if (canCopy()) {
8074 info.addAction(AccessibilityNodeInfo.ACTION_COPY);
8075 }
8076 if (canPaste()) {
8077 info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
8078 }
8079 if (canCut()) {
8080 info.addAction(AccessibilityNodeInfo.ACTION_CUT);
8081 }
8082 }
8083 }
8084
8085 @Override
8086 public boolean performAccessibilityAction(int action, Bundle arguments) {
8087 switch (action) {
8088 case AccessibilityNodeInfo.ACTION_COPY: {
8089 if (isFocused() && canCopy()) {
8090 if (onTextContextMenuItem(ID_COPY)) {
8091 notifyAccessibilityStateChanged();
8092 return true;
8093 }
8094 }
8095 } return false;
8096 case AccessibilityNodeInfo.ACTION_PASTE: {
8097 if (isFocused() && canPaste()) {
8098 if (onTextContextMenuItem(ID_PASTE)) {
8099 notifyAccessibilityStateChanged();
8100 return true;
8101 }
8102 }
8103 } return false;
8104 case AccessibilityNodeInfo.ACTION_CUT: {
8105 if (isFocused() && canCut()) {
8106 if (onTextContextMenuItem(ID_CUT)) {
8107 notifyAccessibilityStateChanged();
8108 return true;
8109 }
8110 }
8111 } return false;
8112 case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
8113 if (isFocused() && canSelectText()) {
Svetoslav7c512842013-01-30 23:02:08 -08008114 CharSequence text = getIterableTextForAccessibility();
8115 if (text == null) {
8116 return false;
8117 }
Svetoslavd0c83cc2013-02-04 18:39:59 -08008118 final int start = (arguments != null) ? arguments.getInt(
8119 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
8120 final int end = (arguments != null) ? arguments.getInt(
8121 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
8122 if ((getSelectionStart() != start || getSelectionEnd() != end)) {
8123 // No arguments clears the selection.
8124 if (start == end && end == -1) {
8125 Selection.removeSelection((Spannable) text);
8126 notifyAccessibilityStateChanged();
8127 return true;
Svetoslav7c512842013-01-30 23:02:08 -08008128 }
Svetoslavd0c83cc2013-02-04 18:39:59 -08008129 if (start >= 0 && start <= end && end <= text.length()) {
8130 Selection.setSelection((Spannable) text, start, end);
8131 // Make sure selection mode is engaged.
8132 if (mEditor != null) {
8133 mEditor.startSelectionActionMode();
8134 }
8135 notifyAccessibilityStateChanged();
8136 return true;
8137 }
Svetoslav7c512842013-01-30 23:02:08 -08008138 }
8139 }
8140 } return false;
8141 default: {
8142 return super.performAccessibilityAction(action, arguments);
8143 }
8144 }
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07008145 }
8146
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07008147 @Override
8148 public void sendAccessibilityEvent(int eventType) {
8149 // Do not send scroll events since first they are not interesting for
8150 // accessibility and second such events a generated too frequently.
8151 // For details see the implementation of bringTextIntoView().
8152 if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
8153 return;
8154 }
8155 super.sendAccessibilityEvent(eventType);
8156 }
8157
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07008158 /**
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008159 * Gets the text reported for accessibility purposes.
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07008160 *
8161 * @return The accessibility text.
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008162 *
8163 * @hide
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07008164 */
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008165 public CharSequence getTextForAccessibility() {
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07008166 CharSequence text = getText();
8167 if (TextUtils.isEmpty(text)) {
8168 text = getHint();
8169 }
8170 return text;
8171 }
8172
svetoslavganov75986cf2009-05-14 22:28:01 -07008173 void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
8174 int fromIndex, int removedCount, int addedCount) {
8175 AccessibilityEvent event =
8176 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
8177 event.setFromIndex(fromIndex);
8178 event.setRemovedCount(removedCount);
8179 event.setAddedCount(addedCount);
8180 event.setBeforeText(beforeText);
8181 sendAccessibilityEventUnchecked(event);
8182 }
8183
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008184 /**
8185 * Returns whether this text view is a current input method target. The
8186 * default implementation just checks with {@link InputMethodManager}.
8187 */
8188 public boolean isInputMethodTarget() {
8189 InputMethodManager imm = InputMethodManager.peekInstance();
8190 return imm != null && imm.isActive(this);
8191 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07008192
Gilles Debunned88876a2012-03-16 17:34:04 -07008193 static final int ID_SELECT_ALL = android.R.id.selectAll;
8194 static final int ID_CUT = android.R.id.cut;
8195 static final int ID_COPY = android.R.id.copy;
8196 static final int ID_PASTE = android.R.id.paste;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008197
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008198 /**
8199 * Called when a context menu option for the text view is selected. Currently
Gilles Debunne07194e52011-11-02 14:18:44 -07008200 * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
8201 * {@link android.R.id#copy} or {@link android.R.id#paste}.
Gilles Debunnec59269f2011-04-22 11:46:09 -07008202 *
8203 * @return true if the context menu item action was performed.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008204 */
8205 public boolean onTextContextMenuItem(int id) {
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07008206 int min = 0;
8207 int max = mText.length();
Gilles Debunne64e54a62010-09-07 19:07:17 -07008208
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07008209 if (isFocused()) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07008210 final int selStart = getSelectionStart();
8211 final int selEnd = getSelectionEnd();
8212
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07008213 min = Math.max(0, Math.min(selStart, selEnd));
8214 max = Math.max(0, Math.max(selStart, selEnd));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008215 }
8216
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008217 switch (id) {
Jeff Brownc1df9072010-12-21 16:38:50 -08008218 case ID_SELECT_ALL:
Gilles Debunne299733e2011-02-07 17:11:41 -08008219 // This does not enter text selection mode. Text is highlighted, so that it can be
Gilles Debunnec59269f2011-04-22 11:46:09 -07008220 // bulk edited, like selectAllOnFocus does. Returns true even if text is empty.
Gilles Debunned88876a2012-03-16 17:34:04 -07008221 selectAllText();
Jeff Brownc1df9072010-12-21 16:38:50 -08008222 return true;
8223
8224 case ID_PASTE:
8225 paste(min, max);
8226 return true;
8227
8228 case ID_CUT:
Gilles Debunnecf68fee2011-09-29 10:55:36 -07008229 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
Gilles Debunne39ba6d92011-11-09 05:26:26 +01008230 deleteText_internal(min, max);
Jeff Brownc1df9072010-12-21 16:38:50 -08008231 stopSelectionActionMode();
8232 return true;
8233
8234 case ID_COPY:
Gilles Debunnecf68fee2011-09-29 10:55:36 -07008235 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
Jeff Brownc1df9072010-12-21 16:38:50 -08008236 stopSelectionActionMode();
8237 return true;
8238 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008239 return false;
8240 }
8241
Gilles Debunned88876a2012-03-16 17:34:04 -07008242 CharSequence getTransformedText(int start, int end) {
Gilles Debunnecf68fee2011-09-29 10:55:36 -07008243 return removeSuggestionSpans(mTransformed.subSequence(start, end));
8244 }
8245
Gilles Debunnee15b3582010-06-16 15:17:21 -07008246 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008247 public boolean performLongClick() {
Gilles Debunnee28454a2011-09-07 18:03:44 -07008248 boolean handled = false;
Gilles Debunnee28454a2011-09-07 18:03:44 -07008249
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008250 if (super.performLongClick()) {
Gilles Debunnee28454a2011-09-07 18:03:44 -07008251 handled = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008252 }
Gilles Debunnef170a342010-11-11 11:08:59 -08008253
Gilles Debunned88876a2012-03-16 17:34:04 -07008254 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008255 handled |= mEditor.performLongClick(handled);
Gilles Debunnee28454a2011-09-07 18:03:44 -07008256 }
8257
Gilles Debunne9f102ca2012-02-28 11:15:54 -08008258 if (handled) {
Gilles Debunnee28454a2011-09-07 18:03:44 -07008259 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
Gilles Debunne2d373a12012-04-20 15:32:19 -07008260 if (mEditor != null) mEditor.mDiscardNextActionUp = true;
Gilles Debunnef788a9f2010-07-22 10:17:23 -07008261 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008262
Gilles Debunne299733e2011-02-07 17:11:41 -08008263 return handled;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008264 }
8265
Gilles Debunne60e21862012-01-30 15:04:14 -08008266 @Override
8267 protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
8268 super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
Gilles Debunne6382ade2012-02-29 15:22:32 -08008269 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008270 mEditor.onScrollChanged();
Gilles Debunne60e21862012-01-30 15:04:14 -08008271 }
8272 }
8273
8274 /**
Gilles Debunne60e21862012-01-30 15:04:14 -08008275 * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
8276 * by the IME or by the spell checker as the user types. This is done by adding
8277 * {@link SuggestionSpan}s to the text.
8278 *
8279 * When suggestions are enabled (default), this list of suggestions will be displayed when the
8280 * user asks for them on these parts of the text. This value depends on the inputType of this
8281 * TextView.
8282 *
8283 * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
8284 *
8285 * In addition, the type variation must be one of
8286 * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
8287 * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
8288 * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
8289 * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
8290 * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
8291 *
8292 * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
8293 *
8294 * @return true if the suggestions popup window is enabled, based on the inputType.
8295 */
8296 public boolean isSuggestionsEnabled() {
8297 if (mEditor == null) return false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07008298 if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
8299 return false;
8300 }
8301 if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
Gilles Debunne60e21862012-01-30 15:04:14 -08008302
Gilles Debunne2d373a12012-04-20 15:32:19 -07008303 final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
Gilles Debunne60e21862012-01-30 15:04:14 -08008304 return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL ||
8305 variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT ||
8306 variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE ||
8307 variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE ||
8308 variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
8309 }
8310
8311 /**
8312 * If provided, this ActionMode.Callback will be used to create the ActionMode when text
8313 * selection is initiated in this View.
8314 *
8315 * The standard implementation populates the menu with a subset of Select All, Cut, Copy and
8316 * Paste actions, depending on what this View supports.
8317 *
8318 * A custom implementation can add new entries in the default menu in its
8319 * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, Menu)} method. The
8320 * default actions can also be removed from the menu using {@link Menu#removeItem(int)} and
8321 * passing {@link android.R.id#selectAll}, {@link android.R.id#cut}, {@link android.R.id#copy}
8322 * or {@link android.R.id#paste} ids as parameters.
8323 *
8324 * Returning false from
8325 * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, Menu)} will prevent
8326 * the action mode from being started.
8327 *
8328 * Action click events should be handled by the custom implementation of
8329 * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, MenuItem)}.
8330 *
8331 * Note that text selection mode is not started when a TextView receives focus and the
8332 * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
8333 * that case, to allow for quick replacement.
8334 */
8335 public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07008336 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07008337 mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
Gilles Debunne60e21862012-01-30 15:04:14 -08008338 }
8339
8340 /**
8341 * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
8342 *
8343 * @return The current custom selection callback.
8344 */
8345 public ActionMode.Callback getCustomSelectionActionModeCallback() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008346 return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
Gilles Debunne60e21862012-01-30 15:04:14 -08008347 }
8348
8349 /**
Gilles Debunne60e21862012-01-30 15:04:14 -08008350 * @hide
8351 */
8352 protected void stopSelectionActionMode() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008353 mEditor.stopSelectionActionMode();
Gilles Debunned88876a2012-03-16 17:34:04 -07008354 }
8355
8356 boolean canCut() {
8357 if (hasPasswordTransformationMethod()) {
8358 return false;
Gilles Debunne60e21862012-01-30 15:04:14 -08008359 }
Gilles Debunned88876a2012-03-16 17:34:04 -07008360
Gilles Debunne2d373a12012-04-20 15:32:19 -07008361 if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null &&
8362 mEditor.mKeyListener != null) {
Gilles Debunned88876a2012-03-16 17:34:04 -07008363 return true;
8364 }
8365
8366 return false;
8367 }
8368
8369 boolean canCopy() {
8370 if (hasPasswordTransformationMethod()) {
8371 return false;
8372 }
8373
8374 if (mText.length() > 0 && hasSelection()) {
8375 return true;
8376 }
8377
8378 return false;
8379 }
8380
8381 boolean canPaste() {
8382 return (mText instanceof Editable &&
Gilles Debunne2d373a12012-04-20 15:32:19 -07008383 mEditor != null && mEditor.mKeyListener != null &&
Gilles Debunned88876a2012-03-16 17:34:04 -07008384 getSelectionStart() >= 0 &&
8385 getSelectionEnd() >= 0 &&
8386 ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
8387 hasPrimaryClip());
8388 }
8389
8390 boolean selectAllText() {
8391 final int length = mText.length();
8392 Selection.setSelection((Spannable) mText, 0, length);
8393 return length > 0;
8394 }
8395
8396 /**
8397 * Prepare text so that there are not zero or two spaces at beginning and end of region defined
8398 * by [min, max] when replacing this region by paste.
8399 * Note that if there were two spaces (or more) at that position before, they are kept. We just
8400 * make sure we do not add an extra one from the paste content.
8401 */
8402 long prepareSpacesAroundPaste(int min, int max, CharSequence paste) {
8403 if (paste.length() > 0) {
8404 if (min > 0) {
8405 final char charBefore = mTransformed.charAt(min - 1);
8406 final char charAfter = paste.charAt(0);
8407
8408 if (Character.isSpaceChar(charBefore) && Character.isSpaceChar(charAfter)) {
8409 // Two spaces at beginning of paste: remove one
8410 final int originalLength = mText.length();
8411 deleteText_internal(min - 1, min);
8412 // Due to filters, there is no guarantee that exactly one character was
8413 // removed: count instead.
8414 final int delta = mText.length() - originalLength;
8415 min += delta;
8416 max += delta;
8417 } else if (!Character.isSpaceChar(charBefore) && charBefore != '\n' &&
8418 !Character.isSpaceChar(charAfter) && charAfter != '\n') {
8419 // No space at beginning of paste: add one
8420 final int originalLength = mText.length();
8421 replaceText_internal(min, min, " ");
8422 // Taking possible filters into account as above.
8423 final int delta = mText.length() - originalLength;
8424 min += delta;
8425 max += delta;
8426 }
8427 }
8428
8429 if (max < mText.length()) {
8430 final char charBefore = paste.charAt(paste.length() - 1);
8431 final char charAfter = mTransformed.charAt(max);
8432
8433 if (Character.isSpaceChar(charBefore) && Character.isSpaceChar(charAfter)) {
8434 // Two spaces at end of paste: remove one
8435 deleteText_internal(max, max + 1);
8436 } else if (!Character.isSpaceChar(charBefore) && charBefore != '\n' &&
8437 !Character.isSpaceChar(charAfter) && charAfter != '\n') {
8438 // No space at end of paste: add one
8439 replaceText_internal(max, max, " ");
8440 }
8441 }
8442 }
8443
8444 return TextUtils.packRangeInLong(min, max);
Gilles Debunne60e21862012-01-30 15:04:14 -08008445 }
8446
8447 /**
8448 * Paste clipboard content between min and max positions.
8449 */
8450 private void paste(int min, int max) {
8451 ClipboardManager clipboard =
8452 (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
8453 ClipData clip = clipboard.getPrimaryClip();
8454 if (clip != null) {
8455 boolean didFirst = false;
8456 for (int i=0; i<clip.getItemCount(); i++) {
Dianne Hackbornacb69bb2012-04-13 15:36:06 -07008457 CharSequence paste = clip.getItemAt(i).coerceToStyledText(getContext());
Gilles Debunne60e21862012-01-30 15:04:14 -08008458 if (paste != null) {
8459 if (!didFirst) {
8460 long minMax = prepareSpacesAroundPaste(min, max, paste);
Gilles Debunne6c488de2012-03-01 16:20:35 -08008461 min = TextUtils.unpackRangeStartFromLong(minMax);
8462 max = TextUtils.unpackRangeEndFromLong(minMax);
Gilles Debunne60e21862012-01-30 15:04:14 -08008463 Selection.setSelection((Spannable) mText, max);
8464 ((Editable) mText).replace(min, max, paste);
8465 didFirst = true;
8466 } else {
8467 ((Editable) mText).insert(getSelectionEnd(), "\n");
8468 ((Editable) mText).insert(getSelectionEnd(), paste);
8469 }
8470 }
8471 }
8472 stopSelectionActionMode();
8473 LAST_CUT_OR_COPY_TIME = 0;
8474 }
8475 }
8476
8477 private void setPrimaryClip(ClipData clip) {
8478 ClipboardManager clipboard = (ClipboardManager) getContext().
8479 getSystemService(Context.CLIPBOARD_SERVICE);
8480 clipboard.setPrimaryClip(clip);
8481 LAST_CUT_OR_COPY_TIME = SystemClock.uptimeMillis();
8482 }
8483
Gilles Debunne60e21862012-01-30 15:04:14 -08008484 /**
8485 * Get the character offset closest to the specified absolute position. A typical use case is to
8486 * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
8487 *
8488 * @param x The horizontal absolute position of a point on screen
8489 * @param y The vertical absolute position of a point on screen
8490 * @return the character offset for the character whose position is closest to the specified
8491 * position. Returns -1 if there is no layout.
8492 */
8493 public int getOffsetForPosition(float x, float y) {
8494 if (getLayout() == null) return -1;
8495 final int line = getLineAtCoordinate(y);
8496 final int offset = getOffsetAtCoordinate(line, x);
8497 return offset;
8498 }
8499
Gilles Debunned88876a2012-03-16 17:34:04 -07008500 float convertToLocalHorizontalCoordinate(float x) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008501 x -= getTotalPaddingLeft();
8502 // Clamp the position to inside of the view.
8503 x = Math.max(0.0f, x);
8504 x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
8505 x += getScrollX();
8506 return x;
8507 }
8508
Gilles Debunned88876a2012-03-16 17:34:04 -07008509 int getLineAtCoordinate(float y) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008510 y -= getTotalPaddingTop();
8511 // Clamp the position to inside of the view.
8512 y = Math.max(0.0f, y);
8513 y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
8514 y += getScrollY();
8515 return getLayout().getLineForVertical((int) y);
8516 }
8517
8518 private int getOffsetAtCoordinate(int line, float x) {
8519 x = convertToLocalHorizontalCoordinate(x);
8520 return getLayout().getOffsetForHorizontal(line, x);
8521 }
8522
Gilles Debunne60e21862012-01-30 15:04:14 -08008523 @Override
8524 public boolean onDragEvent(DragEvent event) {
8525 switch (event.getAction()) {
8526 case DragEvent.ACTION_DRAG_STARTED:
Gilles Debunne2d373a12012-04-20 15:32:19 -07008527 return mEditor != null && mEditor.hasInsertionController();
Gilles Debunne60e21862012-01-30 15:04:14 -08008528
8529 case DragEvent.ACTION_DRAG_ENTERED:
8530 TextView.this.requestFocus();
8531 return true;
8532
8533 case DragEvent.ACTION_DRAG_LOCATION:
8534 final int offset = getOffsetForPosition(event.getX(), event.getY());
8535 Selection.setSelection((Spannable)mText, offset);
8536 return true;
8537
8538 case DragEvent.ACTION_DROP:
Gilles Debunne2d373a12012-04-20 15:32:19 -07008539 if (mEditor != null) mEditor.onDrop(event);
Gilles Debunne60e21862012-01-30 15:04:14 -08008540 return true;
8541
8542 case DragEvent.ACTION_DRAG_ENDED:
8543 case DragEvent.ACTION_DRAG_EXITED:
8544 default:
8545 return true;
8546 }
8547 }
8548
Gilles Debunne60e21862012-01-30 15:04:14 -08008549 boolean isInBatchEditMode() {
8550 if (mEditor == null) return false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07008551 final Editor.InputMethodState ims = mEditor.mInputMethodState;
Gilles Debunne60e21862012-01-30 15:04:14 -08008552 if (ims != null) {
8553 return ims.mBatchEditNesting > 0;
8554 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07008555 return mEditor.mInBatchEditControllers;
Gilles Debunne60e21862012-01-30 15:04:14 -08008556 }
8557
Fabrice Di Meglioa423f502013-05-14 13:20:32 -07008558 @Override
8559 public void onRtlPropertiesChanged(int layoutDirection) {
8560 super.onRtlPropertiesChanged(layoutDirection);
8561
8562 mTextDir = getTextDirectionHeuristic();
8563 }
8564
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008565 TextDirectionHeuristic getTextDirectionHeuristic() {
Gilles Debunne60e21862012-01-30 15:04:14 -08008566 if (hasPasswordTransformationMethod()) {
Fabrice Di Meglio8701bb92012-11-14 19:57:11 -08008567 // passwords fields should be LTR
8568 return TextDirectionHeuristics.LTR;
Gilles Debunne60e21862012-01-30 15:04:14 -08008569 }
8570
8571 // Always need to resolve layout direction first
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07008572 final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
Gilles Debunne60e21862012-01-30 15:04:14 -08008573
8574 // Now, we can select the heuristic
Fabrice Di Meglio97e146c2012-09-23 15:45:16 -07008575 switch (getTextDirection()) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008576 default:
8577 case TEXT_DIRECTION_FIRST_STRONG:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008578 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
Gilles Debunne60e21862012-01-30 15:04:14 -08008579 TextDirectionHeuristics.FIRSTSTRONG_LTR);
Gilles Debunne60e21862012-01-30 15:04:14 -08008580 case TEXT_DIRECTION_ANY_RTL:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008581 return TextDirectionHeuristics.ANYRTL_LTR;
Gilles Debunne60e21862012-01-30 15:04:14 -08008582 case TEXT_DIRECTION_LTR:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008583 return TextDirectionHeuristics.LTR;
Gilles Debunne60e21862012-01-30 15:04:14 -08008584 case TEXT_DIRECTION_RTL:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008585 return TextDirectionHeuristics.RTL;
Gilles Debunne60e21862012-01-30 15:04:14 -08008586 case TEXT_DIRECTION_LOCALE:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008587 return TextDirectionHeuristics.LOCALE;
Gilles Debunne60e21862012-01-30 15:04:14 -08008588 }
8589 }
8590
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008591 /**
8592 * @hide
8593 */
Fabrice Di Megliob03b4342012-06-04 12:55:30 -07008594 @Override
8595 public void onResolveDrawables(int layoutDirection) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008596 // No need to resolve twice
Fabrice Di Meglio1957d282012-10-25 17:42:39 -07008597 if (mLastLayoutDirection == layoutDirection) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008598 return;
8599 }
Fabrice Di Meglio1957d282012-10-25 17:42:39 -07008600 mLastLayoutDirection = layoutDirection;
Gilles Debunne60e21862012-01-30 15:04:14 -08008601
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -08008602 // Resolve drawables
8603 if (mDrawables != null) {
8604 mDrawables.resolveWithLayoutDirection(layoutDirection);
Fabrice Di Megliob03b4342012-06-04 12:55:30 -07008605 }
8606 }
8607
Fabrice Di Meglio84ebb352012-10-11 16:27:37 -07008608 /**
8609 * @hide
8610 */
Gilles Debunne60e21862012-01-30 15:04:14 -08008611 protected void resetResolvedDrawables() {
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -08008612 super.resetResolvedDrawables();
Fabrice Di Meglio1957d282012-10-25 17:42:39 -07008613 mLastLayoutDirection = -1;
Gilles Debunne60e21862012-01-30 15:04:14 -08008614 }
8615
8616 /**
8617 * @hide
8618 */
8619 protected void viewClicked(InputMethodManager imm) {
8620 if (imm != null) {
8621 imm.viewClicked(this);
8622 }
8623 }
8624
8625 /**
8626 * Deletes the range of text [start, end[.
8627 * @hide
8628 */
8629 protected void deleteText_internal(int start, int end) {
8630 ((Editable) mText).delete(start, end);
8631 }
8632
8633 /**
8634 * Replaces the range of text [start, end[ by replacement text
8635 * @hide
8636 */
8637 protected void replaceText_internal(int start, int end, CharSequence text) {
8638 ((Editable) mText).replace(start, end, text);
8639 }
8640
8641 /**
8642 * Sets a span on the specified range of text
8643 * @hide
8644 */
8645 protected void setSpan_internal(Object span, int start, int end, int flags) {
8646 ((Editable) mText).setSpan(span, start, end, flags);
8647 }
8648
8649 /**
8650 * Moves the cursor to the specified offset position in text
8651 * @hide
8652 */
8653 protected void setCursorPosition_internal(int start, int end) {
8654 Selection.setSelection(((Editable) mText), start, end);
8655 }
8656
8657 /**
8658 * An Editor should be created as soon as any of the editable-specific fields (grouped
8659 * inside the Editor object) is assigned to a non-default value.
8660 * This method will create the Editor if needed.
8661 *
8662 * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
8663 * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
8664 * Editor for backward compatibility, as soon as one of these fields is assigned.
8665 *
8666 * Also note that for performance reasons, the mEditor is created when needed, but not
8667 * reset when no more edit-specific fields are needed.
8668 */
Gilles Debunne5fae9962012-05-08 14:53:20 -07008669 private void createEditorIfNeeded() {
Gilles Debunne60e21862012-01-30 15:04:14 -08008670 if (mEditor == null) {
Gilles Debunned88876a2012-03-16 17:34:04 -07008671 mEditor = new Editor(this);
Gilles Debunne60e21862012-01-30 15:04:14 -08008672 }
8673 }
8674
Gilles Debunne60e21862012-01-30 15:04:14 -08008675 /**
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008676 * @hide
8677 */
8678 @Override
8679 public CharSequence getIterableTextForAccessibility() {
Svetoslavdb7da0e2013-04-22 18:34:02 -07008680 if (!(mText instanceof Spannable)) {
8681 setText(mText, BufferType.SPANNABLE);
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008682 }
Svetoslavdb7da0e2013-04-22 18:34:02 -07008683 return mText;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008684 }
8685
8686 /**
8687 * @hide
8688 */
8689 @Override
8690 public TextSegmentIterator getIteratorForGranularity(int granularity) {
8691 switch (granularity) {
8692 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
8693 Spannable text = (Spannable) getIterableTextForAccessibility();
8694 if (!TextUtils.isEmpty(text) && getLayout() != null) {
8695 AccessibilityIterators.LineTextSegmentIterator iterator =
8696 AccessibilityIterators.LineTextSegmentIterator.getInstance();
8697 iterator.initialize(text, getLayout());
8698 return iterator;
8699 }
8700 } break;
8701 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
8702 Spannable text = (Spannable) getIterableTextForAccessibility();
8703 if (!TextUtils.isEmpty(text) && getLayout() != null) {
8704 AccessibilityIterators.PageTextSegmentIterator iterator =
8705 AccessibilityIterators.PageTextSegmentIterator.getInstance();
8706 iterator.initialize(this);
8707 return iterator;
8708 }
8709 } break;
8710 }
8711 return super.getIteratorForGranularity(granularity);
8712 }
8713
8714 /**
8715 * @hide
8716 */
8717 @Override
Svetoslav7c512842013-01-30 23:02:08 -08008718 public int getAccessibilitySelectionStart() {
Svetoslavdb7da0e2013-04-22 18:34:02 -07008719 return getSelectionStart();
Svetoslav7c512842013-01-30 23:02:08 -08008720 }
8721
8722 /**
8723 * @hide
8724 */
8725 public boolean isAccessibilitySelectionExtendable() {
8726 return true;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008727 }
8728
8729 /**
8730 * @hide
8731 */
8732 @Override
Svetoslav7c512842013-01-30 23:02:08 -08008733 public int getAccessibilitySelectionEnd() {
Svetoslavdb7da0e2013-04-22 18:34:02 -07008734 return getSelectionEnd();
Svetoslav7c512842013-01-30 23:02:08 -08008735 }
8736
8737 /**
8738 * @hide
8739 */
8740 @Override
8741 public void setAccessibilitySelection(int start, int end) {
8742 if (getAccessibilitySelectionStart() == start
8743 && getAccessibilitySelectionEnd() == end) {
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008744 return;
8745 }
Svetoslavabad55d2013-05-07 18:49:51 -07008746 // Hide all selection controllers used for adjusting selection
8747 // since we are doing so explicitlty by other means and these
8748 // controllers interact with how selection behaves.
8749 if (mEditor != null) {
8750 mEditor.hideControllers();
8751 }
Svetoslav7c512842013-01-30 23:02:08 -08008752 CharSequence text = getIterableTextForAccessibility();
Svetoslavabad55d2013-05-07 18:49:51 -07008753 if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
Svetoslav7c512842013-01-30 23:02:08 -08008754 Selection.setSelection((Spannable) text, start, end);
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008755 } else {
Svetoslav7c512842013-01-30 23:02:08 -08008756 Selection.removeSelection((Spannable) text);
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008757 }
8758 }
8759
8760 /**
Gilles Debunne60e21862012-01-30 15:04:14 -08008761 * User interface state that is stored by TextView for implementing
8762 * {@link View#onSaveInstanceState}.
8763 */
8764 public static class SavedState extends BaseSavedState {
8765 int selStart;
8766 int selEnd;
8767 CharSequence text;
8768 boolean frozenWithFocus;
8769 CharSequence error;
8770
8771 SavedState(Parcelable superState) {
8772 super(superState);
8773 }
8774
8775 @Override
8776 public void writeToParcel(Parcel out, int flags) {
8777 super.writeToParcel(out, flags);
8778 out.writeInt(selStart);
8779 out.writeInt(selEnd);
8780 out.writeInt(frozenWithFocus ? 1 : 0);
8781 TextUtils.writeToParcel(text, out, flags);
8782
8783 if (error == null) {
8784 out.writeInt(0);
8785 } else {
8786 out.writeInt(1);
8787 TextUtils.writeToParcel(error, out, flags);
8788 }
8789 }
8790
8791 @Override
8792 public String toString() {
8793 String str = "TextView.SavedState{"
8794 + Integer.toHexString(System.identityHashCode(this))
8795 + " start=" + selStart + " end=" + selEnd;
8796 if (text != null) {
8797 str += " text=" + text;
8798 }
8799 return str + "}";
8800 }
8801
8802 @SuppressWarnings("hiding")
8803 public static final Parcelable.Creator<SavedState> CREATOR
8804 = new Parcelable.Creator<SavedState>() {
8805 public SavedState createFromParcel(Parcel in) {
8806 return new SavedState(in);
8807 }
8808
8809 public SavedState[] newArray(int size) {
8810 return new SavedState[size];
8811 }
8812 };
8813
8814 private SavedState(Parcel in) {
8815 super(in);
8816 selStart = in.readInt();
8817 selEnd = in.readInt();
8818 frozenWithFocus = (in.readInt() != 0);
8819 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
8820
8821 if (in.readInt() != 0) {
8822 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
8823 }
8824 }
8825 }
8826
8827 private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
8828 private char[] mChars;
8829 private int mStart, mLength;
8830
8831 public CharWrapper(char[] chars, int start, int len) {
8832 mChars = chars;
8833 mStart = start;
8834 mLength = len;
8835 }
8836
8837 /* package */ void set(char[] chars, int start, int len) {
8838 mChars = chars;
8839 mStart = start;
8840 mLength = len;
8841 }
8842
8843 public int length() {
8844 return mLength;
8845 }
8846
8847 public char charAt(int off) {
8848 return mChars[off + mStart];
8849 }
8850
8851 @Override
8852 public String toString() {
8853 return new String(mChars, mStart, mLength);
8854 }
8855
8856 public CharSequence subSequence(int start, int end) {
8857 if (start < 0 || end < 0 || start > mLength || end > mLength) {
8858 throw new IndexOutOfBoundsException(start + ", " + end);
8859 }
8860
8861 return new String(mChars, start + mStart, end - start);
8862 }
8863
8864 public void getChars(int start, int end, char[] buf, int off) {
8865 if (start < 0 || end < 0 || start > mLength || end > mLength) {
8866 throw new IndexOutOfBoundsException(start + ", " + end);
8867 }
8868
8869 System.arraycopy(mChars, start + mStart, buf, off, end - start);
8870 }
8871
8872 public void drawText(Canvas c, int start, int end,
8873 float x, float y, Paint p) {
8874 c.drawText(mChars, start + mStart, end - start, x, y, p);
8875 }
8876
8877 public void drawTextRun(Canvas c, int start, int end,
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07008878 int contextStart, int contextEnd, float x, float y, int flags, Paint p) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008879 int count = end - start;
8880 int contextCount = contextEnd - contextStart;
8881 c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07008882 contextCount, x, y, flags, p);
Gilles Debunne60e21862012-01-30 15:04:14 -08008883 }
8884
8885 public float measureText(int start, int end, Paint p) {
8886 return p.measureText(mChars, start + mStart, end - start);
8887 }
8888
8889 public int getTextWidths(int start, int end, float[] widths, Paint p) {
8890 return p.getTextWidths(mChars, start + mStart, end - start, widths);
8891 }
8892
8893 public float getTextRunAdvances(int start, int end, int contextStart,
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07008894 int contextEnd, int flags, float[] advances, int advancesIndex,
Gilles Debunne60e21862012-01-30 15:04:14 -08008895 Paint p) {
8896 int count = end - start;
8897 int contextCount = contextEnd - contextStart;
8898 return p.getTextRunAdvances(mChars, start + mStart, count,
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07008899 contextStart + mStart, contextCount, flags, advances,
Gilles Debunne60e21862012-01-30 15:04:14 -08008900 advancesIndex);
8901 }
8902
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07008903 public int getTextRunCursor(int contextStart, int contextEnd, int flags,
Gilles Debunne60e21862012-01-30 15:04:14 -08008904 int offset, int cursorOpt, Paint p) {
8905 int contextCount = contextEnd - contextStart;
8906 return p.getTextRunCursor(mChars, contextStart + mStart,
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07008907 contextCount, flags, offset + mStart, cursorOpt);
Gilles Debunne60e21862012-01-30 15:04:14 -08008908 }
8909 }
8910
Gilles Debunne60e21862012-01-30 15:04:14 -08008911 private static final class Marquee extends Handler {
8912 // TODO: Add an option to configure this
8913 private static final float MARQUEE_DELTA_MAX = 0.07f;
8914 private static final int MARQUEE_DELAY = 1200;
8915 private static final int MARQUEE_RESTART_DELAY = 1200;
8916 private static final int MARQUEE_RESOLUTION = 1000 / 30;
8917 private static final int MARQUEE_PIXELS_PER_SECOND = 30;
8918
8919 private static final byte MARQUEE_STOPPED = 0x0;
8920 private static final byte MARQUEE_STARTING = 0x1;
8921 private static final byte MARQUEE_RUNNING = 0x2;
8922
8923 private static final int MESSAGE_START = 0x1;
8924 private static final int MESSAGE_TICK = 0x2;
8925 private static final int MESSAGE_RESTART = 0x3;
8926
8927 private final WeakReference<TextView> mView;
8928
8929 private byte mStatus = MARQUEE_STOPPED;
8930 private final float mScrollUnit;
8931 private float mMaxScroll;
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07008932 private float mMaxFadeScroll;
Gilles Debunne60e21862012-01-30 15:04:14 -08008933 private float mGhostStart;
8934 private float mGhostOffset;
8935 private float mFadeStop;
8936 private int mRepeatLimit;
8937
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07008938 private float mScroll;
Gilles Debunne60e21862012-01-30 15:04:14 -08008939
8940 Marquee(TextView v) {
8941 final float density = v.getContext().getResources().getDisplayMetrics().density;
8942 mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / MARQUEE_RESOLUTION;
8943 mView = new WeakReference<TextView>(v);
8944 }
8945
8946 @Override
8947 public void handleMessage(Message msg) {
8948 switch (msg.what) {
8949 case MESSAGE_START:
8950 mStatus = MARQUEE_RUNNING;
8951 tick();
8952 break;
8953 case MESSAGE_TICK:
8954 tick();
8955 break;
8956 case MESSAGE_RESTART:
8957 if (mStatus == MARQUEE_RUNNING) {
8958 if (mRepeatLimit >= 0) {
8959 mRepeatLimit--;
8960 }
8961 start(mRepeatLimit);
8962 }
8963 break;
8964 }
8965 }
8966
8967 void tick() {
8968 if (mStatus != MARQUEE_RUNNING) {
8969 return;
8970 }
8971
8972 removeMessages(MESSAGE_TICK);
8973
8974 final TextView textView = mView.get();
8975 if (textView != null && (textView.isFocused() || textView.isSelected())) {
8976 mScroll += mScrollUnit;
8977 if (mScroll > mMaxScroll) {
8978 mScroll = mMaxScroll;
8979 sendEmptyMessageDelayed(MESSAGE_RESTART, MARQUEE_RESTART_DELAY);
8980 } else {
8981 sendEmptyMessageDelayed(MESSAGE_TICK, MARQUEE_RESOLUTION);
8982 }
8983 textView.invalidate();
8984 }
8985 }
8986
8987 void stop() {
8988 mStatus = MARQUEE_STOPPED;
8989 removeMessages(MESSAGE_START);
8990 removeMessages(MESSAGE_RESTART);
8991 removeMessages(MESSAGE_TICK);
8992 resetScroll();
8993 }
8994
8995 private void resetScroll() {
8996 mScroll = 0.0f;
8997 final TextView textView = mView.get();
8998 if (textView != null) textView.invalidate();
8999 }
9000
9001 void start(int repeatLimit) {
9002 if (repeatLimit == 0) {
9003 stop();
9004 return;
9005 }
9006 mRepeatLimit = repeatLimit;
9007 final TextView textView = mView.get();
9008 if (textView != null && textView.mLayout != null) {
9009 mStatus = MARQUEE_STARTING;
9010 mScroll = 0.0f;
9011 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
9012 textView.getCompoundPaddingRight();
9013 final float lineWidth = textView.mLayout.getLineWidth(0);
9014 final float gap = textWidth / 3.0f;
9015 mGhostStart = lineWidth - textWidth + gap;
9016 mMaxScroll = mGhostStart + textWidth;
9017 mGhostOffset = lineWidth + gap;
9018 mFadeStop = lineWidth + textWidth / 6.0f;
9019 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
9020
9021 textView.invalidate();
9022 sendEmptyMessageDelayed(MESSAGE_START, MARQUEE_DELAY);
9023 }
9024 }
9025
9026 float getGhostOffset() {
9027 return mGhostOffset;
9028 }
9029
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07009030 float getScroll() {
9031 return mScroll;
9032 }
9033
9034 float getMaxFadeScroll() {
9035 return mMaxFadeScroll;
9036 }
9037
Gilles Debunne60e21862012-01-30 15:04:14 -08009038 boolean shouldDrawLeftFade() {
9039 return mScroll <= mFadeStop;
9040 }
9041
9042 boolean shouldDrawGhost() {
9043 return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
9044 }
9045
9046 boolean isRunning() {
9047 return mStatus == MARQUEE_RUNNING;
9048 }
9049
9050 boolean isStopped() {
9051 return mStatus == MARQUEE_STOPPED;
9052 }
9053 }
9054
Gilles Debunne60e21862012-01-30 15:04:14 -08009055 private class ChangeWatcher implements TextWatcher, SpanWatcher {
9056
9057 private CharSequence mBeforeText;
9058
Gilles Debunne60e21862012-01-30 15:04:14 -08009059 public void beforeTextChanged(CharSequence buffer, int start,
9060 int before, int after) {
9061 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start
9062 + " before=" + before + " after=" + after + ": " + buffer);
9063
9064 if (AccessibilityManager.getInstance(mContext).isEnabled()
Svetoslav Ganov72bba582012-11-05 13:53:43 -08009065 && ((!isPasswordInputType(getInputType()) && !hasPasswordTransformationMethod())
9066 || shouldSpeakPasswordsForAccessibility())) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009067 mBeforeText = buffer.toString();
9068 }
9069
9070 TextView.this.sendBeforeTextChanged(buffer, start, before, after);
9071 }
9072
Gilles Debunned88876a2012-03-16 17:34:04 -07009073 public void onTextChanged(CharSequence buffer, int start, int before, int after) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009074 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start
9075 + " before=" + before + " after=" + after + ": " + buffer);
9076 TextView.this.handleTextChanged(buffer, start, before, after);
9077
Gilles Debunne60e21862012-01-30 15:04:14 -08009078 if (AccessibilityManager.getInstance(mContext).isEnabled() &&
9079 (isFocused() || isSelected() && isShown())) {
9080 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
9081 mBeforeText = null;
9082 }
9083 }
9084
9085 public void afterTextChanged(Editable buffer) {
9086 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer);
9087 TextView.this.sendAfterTextChanged(buffer);
9088
9089 if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
9090 MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
9091 }
9092 }
9093
Gilles Debunned88876a2012-03-16 17:34:04 -07009094 public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009095 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
9096 + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
9097 TextView.this.spanChange(buf, what, s, st, e, en);
9098 }
9099
9100 public void onSpanAdded(Spannable buf, Object what, int s, int e) {
9101 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e
9102 + " what=" + what + ": " + buf);
9103 TextView.this.spanChange(buf, what, -1, s, -1, e);
9104 }
9105
9106 public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
9107 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e
9108 + " what=" + what + ": " + buf);
9109 TextView.this.spanChange(buf, what, s, -1, e, -1);
9110 }
satoka67a3cf2011-09-07 17:14:03 +09009111 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009112}