TextView.java revision 7b7711680244108923f0dbb4ca6e35db7e659e6a
1d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liao/*
2d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liao * Copyright (C) 2006 The Android Open Source Project
3d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liao *
4d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liao * Licensed under the Apache License, Version 2.0 (the "License");
5d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liao * you may not use this file except in compliance with the License.
6d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liao * You may obtain a copy of the License at
7d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liao *
8d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liao *      http://www.apache.org/licenses/LICENSE-2.0
9d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liao *
10d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liao * Unless required by applicable law or agreed to in writing, software
11d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liao * distributed under the License is distributed on an "AS IS" BASIS,
12d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liao * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liao * See the License for the specific language governing permissions and
14d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liao * limitations under the License.
15d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liao */
16d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liao
17d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaopackage android.widget;
18d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liao
19d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaoimport static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
20d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaoimport static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH;
21d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaoimport static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX;
22f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
23f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
24f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hines
25d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaoimport android.R;
26d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaoimport android.annotation.ColorInt;
27d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaoimport android.annotation.DrawableRes;
28d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaoimport android.annotation.FloatRange;
29d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaoimport android.annotation.IntDef;
30d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaoimport android.annotation.NonNull;
31d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaoimport android.annotation.Nullable;
326f75755c9204b1d8817ae5a65a2f7e5af0ec3f70Stephen Hinesimport android.annotation.Size;
33d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaoimport android.annotation.StringRes;
346f75755c9204b1d8817ae5a65a2f7e5af0ec3f70Stephen Hinesimport android.annotation.StyleRes;
356f75755c9204b1d8817ae5a65a2f7e5af0ec3f70Stephen Hinesimport android.annotation.XmlRes;
366f75755c9204b1d8817ae5a65a2f7e5af0ec3f70Stephen Hinesimport android.app.Activity;
37d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaoimport android.app.assist.AssistStructure;
38d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaoimport android.content.ClipData;
39d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaoimport android.content.ClipboardManager;
40d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaoimport android.content.Context;
41d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaoimport android.content.Intent;
42d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaoimport android.content.UndoManager;
43d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaoimport android.content.res.ColorStateList;
44d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaoimport android.content.res.CompatibilityInfo;
45d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaoimport android.content.res.Configuration;
46d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaoimport android.content.res.Resources;
47d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaoimport android.content.res.TypedArray;
48f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.content.res.XmlResourceParser;
49f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.graphics.BaseCanvas;
50f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.graphics.Canvas;
51f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.graphics.Insets;
526f75755c9204b1d8817ae5a65a2f7e5af0ec3f70Stephen Hinesimport android.graphics.Paint;
53d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaoimport android.graphics.Path;
54d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaoimport android.graphics.PorterDuff;
55d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaoimport android.graphics.Rect;
56d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaoimport android.graphics.RectF;
57f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.graphics.Typeface;
58f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.graphics.drawable.Drawable;
59f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.os.AsyncTask;
60f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.os.Bundle;
61f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.os.LocaleList;
62f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.os.Parcel;
63f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.os.Parcelable;
64f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.os.ParcelableParcel;
65f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.os.SystemClock;
66f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.provider.Settings;
67f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.text.BoringLayout;
68f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.text.DynamicLayout;
69f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.text.Editable;
70f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.text.GetChars;
71f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.text.GraphicsOperations;
72f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.text.InputFilter;
73f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.text.InputType;
74f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.text.Layout;
75f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.text.ParcelableSpan;
76f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.text.Selection;
77f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.text.SpanWatcher;
78f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.text.Spannable;
79f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.text.SpannableStringBuilder;
80f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.text.Spanned;
81f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.text.SpannedString;
82f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.text.StaticLayout;
83f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.text.TextDirectionHeuristic;
84f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.text.TextDirectionHeuristics;
85f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.text.TextPaint;
86f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.text.TextUtils;
87f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.text.TextUtils.TruncateAt;
88f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.text.TextWatcher;
89f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.text.method.AllCapsTransformationMethod;
90f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.text.method.ArrowKeyMovementMethod;
91f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.text.method.DateKeyListener;
92f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.text.method.DateTimeKeyListener;
93f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.text.method.DialerKeyListener;
94f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.text.method.DigitsKeyListener;
95f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.text.method.KeyListener;
96f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.text.method.LinkMovementMethod;
97f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.text.method.MetaKeyKeyListener;
98f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.text.method.MovementMethod;
99d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaoimport android.text.method.PasswordTransformationMethod;
100d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaoimport android.text.method.SingleLineTransformationMethod;
101d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaoimport android.text.method.TextKeyListener;
102d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaoimport android.text.method.TimeKeyListener;
103d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaoimport android.text.method.TransformationMethod;
1046f75755c9204b1d8817ae5a65a2f7e5af0ec3f70Stephen Hinesimport android.text.method.TransformationMethod2;
105d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaoimport android.text.method.WordIterator;
106d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaoimport android.text.style.CharacterStyle;
1076f75755c9204b1d8817ae5a65a2f7e5af0ec3f70Stephen Hinesimport android.text.style.ClickableSpan;
1086f75755c9204b1d8817ae5a65a2f7e5af0ec3f70Stephen Hinesimport android.text.style.ParagraphStyle;
109f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.text.style.SpellCheckSpan;
110f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.text.style.SuggestionSpan;
111f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.text.style.URLSpan;
112f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.text.style.UpdateAppearance;
113f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.text.util.Linkify;
114f7ac0f19a1c8d0ad14bcf6456ce368b830fea886Stephen Hinesimport android.util.AttributeSet;
115d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaoimport android.util.DisplayMetrics;
116d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaoimport android.util.IntArray;
117d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaoimport android.util.Log;
118d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaoimport android.util.TypedValue;
119d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaoimport android.view.AccessibilityIterators.TextSegmentIterator;
120d0fbbb227051be16931a1aa9b4a7722ac039c698Shih-wei Liaoimport android.view.ActionMode;
121import android.view.Choreographer;
122import android.view.ContextMenu;
123import android.view.DragEvent;
124import android.view.Gravity;
125import android.view.HapticFeedbackConstants;
126import android.view.KeyCharacterMap;
127import android.view.KeyEvent;
128import android.view.MotionEvent;
129import android.view.PointerIcon;
130import android.view.View;
131import android.view.ViewConfiguration;
132import android.view.ViewDebug;
133import android.view.ViewGroup.LayoutParams;
134import android.view.ViewHierarchyEncoder;
135import android.view.ViewParent;
136import android.view.ViewRootImpl;
137import android.view.ViewStructure;
138import android.view.ViewTreeObserver;
139import android.view.accessibility.AccessibilityEvent;
140import android.view.accessibility.AccessibilityManager;
141import android.view.accessibility.AccessibilityNodeInfo;
142import android.view.animation.AnimationUtils;
143import android.view.autofill.AutofillManager;
144import android.view.autofill.AutofillValue;
145import android.view.inputmethod.BaseInputConnection;
146import android.view.inputmethod.CompletionInfo;
147import android.view.inputmethod.CorrectionInfo;
148import android.view.inputmethod.CursorAnchorInfo;
149import android.view.inputmethod.EditorInfo;
150import android.view.inputmethod.ExtractedText;
151import android.view.inputmethod.ExtractedTextRequest;
152import android.view.inputmethod.InputConnection;
153import android.view.inputmethod.InputMethodManager;
154import android.view.textclassifier.TextClassificationManager;
155import android.view.textclassifier.TextClassifier;
156import android.view.textservice.SpellCheckerSubtype;
157import android.view.textservice.TextServicesManager;
158import android.widget.RemoteViews.RemoteView;
159
160import com.android.internal.annotations.VisibleForTesting;
161import com.android.internal.logging.MetricsLogger;
162import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
163import com.android.internal.util.FastMath;
164import com.android.internal.widget.EditableInputConnection;
165
166import libcore.util.EmptyArray;
167
168import org.xmlpull.v1.XmlPullParserException;
169
170import java.io.IOException;
171import java.lang.annotation.Retention;
172import java.lang.annotation.RetentionPolicy;
173import java.lang.ref.WeakReference;
174import java.util.ArrayList;
175import java.util.Arrays;
176import java.util.Locale;
177
178/**
179 * Displays text to the user and optionally allows them to edit it.  A TextView
180 * is a complete text editor, however the basic class is configured to not
181 * allow editing; see {@link EditText} for a subclass that configures the text
182 * view for editing.
183 *
184 * <p>
185 * To allow users to copy some or all of the TextView's value and paste it somewhere else, set the
186 * XML attribute {@link android.R.styleable#TextView_textIsSelectable
187 * android:textIsSelectable} to "true" or call
188 * {@link #setTextIsSelectable setTextIsSelectable(true)}. The {@code textIsSelectable} flag
189 * allows users to make selection gestures in the TextView, which in turn triggers the system's
190 * built-in copy/paste controls.
191 * <p>
192 * <b>XML attributes</b>
193 * <p>
194 * See {@link android.R.styleable#TextView TextView Attributes},
195 * {@link android.R.styleable#View View Attributes}
196 *
197 * @attr ref android.R.styleable#TextView_text
198 * @attr ref android.R.styleable#TextView_bufferType
199 * @attr ref android.R.styleable#TextView_hint
200 * @attr ref android.R.styleable#TextView_textColor
201 * @attr ref android.R.styleable#TextView_textColorHighlight
202 * @attr ref android.R.styleable#TextView_textColorHint
203 * @attr ref android.R.styleable#TextView_textAppearance
204 * @attr ref android.R.styleable#TextView_textColorLink
205 * @attr ref android.R.styleable#TextView_textSize
206 * @attr ref android.R.styleable#TextView_textScaleX
207 * @attr ref android.R.styleable#TextView_fontFamily
208 * @attr ref android.R.styleable#TextView_typeface
209 * @attr ref android.R.styleable#TextView_textStyle
210 * @attr ref android.R.styleable#TextView_cursorVisible
211 * @attr ref android.R.styleable#TextView_maxLines
212 * @attr ref android.R.styleable#TextView_maxHeight
213 * @attr ref android.R.styleable#TextView_lines
214 * @attr ref android.R.styleable#TextView_height
215 * @attr ref android.R.styleable#TextView_minLines
216 * @attr ref android.R.styleable#TextView_minHeight
217 * @attr ref android.R.styleable#TextView_maxEms
218 * @attr ref android.R.styleable#TextView_maxWidth
219 * @attr ref android.R.styleable#TextView_ems
220 * @attr ref android.R.styleable#TextView_width
221 * @attr ref android.R.styleable#TextView_minEms
222 * @attr ref android.R.styleable#TextView_minWidth
223 * @attr ref android.R.styleable#TextView_gravity
224 * @attr ref android.R.styleable#TextView_scrollHorizontally
225 * @attr ref android.R.styleable#TextView_password
226 * @attr ref android.R.styleable#TextView_singleLine
227 * @attr ref android.R.styleable#TextView_selectAllOnFocus
228 * @attr ref android.R.styleable#TextView_includeFontPadding
229 * @attr ref android.R.styleable#TextView_maxLength
230 * @attr ref android.R.styleable#TextView_shadowColor
231 * @attr ref android.R.styleable#TextView_shadowDx
232 * @attr ref android.R.styleable#TextView_shadowDy
233 * @attr ref android.R.styleable#TextView_shadowRadius
234 * @attr ref android.R.styleable#TextView_autoLink
235 * @attr ref android.R.styleable#TextView_linksClickable
236 * @attr ref android.R.styleable#TextView_numeric
237 * @attr ref android.R.styleable#TextView_digits
238 * @attr ref android.R.styleable#TextView_phoneNumber
239 * @attr ref android.R.styleable#TextView_inputMethod
240 * @attr ref android.R.styleable#TextView_capitalize
241 * @attr ref android.R.styleable#TextView_autoText
242 * @attr ref android.R.styleable#TextView_editable
243 * @attr ref android.R.styleable#TextView_freezesText
244 * @attr ref android.R.styleable#TextView_ellipsize
245 * @attr ref android.R.styleable#TextView_drawableTop
246 * @attr ref android.R.styleable#TextView_drawableBottom
247 * @attr ref android.R.styleable#TextView_drawableRight
248 * @attr ref android.R.styleable#TextView_drawableLeft
249 * @attr ref android.R.styleable#TextView_drawableStart
250 * @attr ref android.R.styleable#TextView_drawableEnd
251 * @attr ref android.R.styleable#TextView_drawablePadding
252 * @attr ref android.R.styleable#TextView_drawableTint
253 * @attr ref android.R.styleable#TextView_drawableTintMode
254 * @attr ref android.R.styleable#TextView_lineSpacingExtra
255 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
256 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
257 * @attr ref android.R.styleable#TextView_inputType
258 * @attr ref android.R.styleable#TextView_imeOptions
259 * @attr ref android.R.styleable#TextView_privateImeOptions
260 * @attr ref android.R.styleable#TextView_imeActionLabel
261 * @attr ref android.R.styleable#TextView_imeActionId
262 * @attr ref android.R.styleable#TextView_editorExtras
263 * @attr ref android.R.styleable#TextView_elegantTextHeight
264 * @attr ref android.R.styleable#TextView_letterSpacing
265 * @attr ref android.R.styleable#TextView_fontFeatureSettings
266 * @attr ref android.R.styleable#TextView_breakStrategy
267 * @attr ref android.R.styleable#TextView_hyphenationFrequency
268 * @attr ref android.R.styleable#TextView_autoSizeTextType
269 * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
270 * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
271 * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
272 * @attr ref android.R.styleable#TextView_autoSizePresetSizes
273 */
274@RemoteView
275public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
276    static final String LOG_TAG = "TextView";
277    static final boolean DEBUG_EXTRACT = false;
278    static final boolean DEBUG_AUTOFILL = false;
279    private static final float[] TEMP_POSITION = new float[2];
280
281    // Enum for the "typeface" XML parameter.
282    // TODO: How can we get this from the XML instead of hardcoding it here?
283    private static final int SANS = 1;
284    private static final int SERIF = 2;
285    private static final int MONOSPACE = 3;
286
287    // Bitfield for the "numeric" XML parameter.
288    // TODO: How can we get this from the XML instead of hardcoding it here?
289    private static final int SIGNED = 2;
290    private static final int DECIMAL = 4;
291
292    /**
293     * Draw marquee text with fading edges as usual
294     */
295    private static final int MARQUEE_FADE_NORMAL = 0;
296
297    /**
298     * Draw marquee text as ellipsize end while inactive instead of with the fade.
299     * (Useful for devices where the fade can be expensive if overdone)
300     */
301    private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1;
302
303    /**
304     * Draw marquee text with fading edges because it is currently active/animating.
305     */
306    private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2;
307
308    private static final int LINES = 1;
309    private static final int EMS = LINES;
310    private static final int PIXELS = 2;
311
312    private static final RectF TEMP_RECTF = new RectF();
313
314    /** @hide */
315    static final int VERY_WIDE = 1024 * 1024; // XXX should be much larger
316    private static final int ANIMATED_SCROLL_GAP = 250;
317
318    private static final InputFilter[] NO_FILTERS = new InputFilter[0];
319    private static final Spanned EMPTY_SPANNED = new SpannedString("");
320
321    private static final int CHANGE_WATCHER_PRIORITY = 100;
322
323    // New state used to change background based on whether this TextView is multiline.
324    private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
325
326    // Accessibility action to share selected text.
327    private static final int ACCESSIBILITY_ACTION_SHARE = 0x10000000;
328
329    /**
330     * @hide
331     */
332    // Accessibility action start id for "process text" actions.
333    static final int ACCESSIBILITY_ACTION_PROCESS_TEXT_START_ID = 0x10000100;
334
335    /**
336     * @hide
337     */
338    static final int PROCESS_TEXT_REQUEST_CODE = 100;
339
340    /**
341     *  Return code of {@link #doKeyDown}.
342     */
343    private static final int KEY_EVENT_NOT_HANDLED = 0;
344    private static final int KEY_EVENT_HANDLED = -1;
345    private static final int KEY_DOWN_HANDLED_BY_KEY_LISTENER = 1;
346    private static final int KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD = 2;
347
348    // System wide time for last cut, copy or text changed action.
349    static long sLastCutCopyOrTextChangedTime;
350
351    private ColorStateList mTextColor;
352    private ColorStateList mHintTextColor;
353    private ColorStateList mLinkTextColor;
354    @ViewDebug.ExportedProperty(category = "text")
355    private int mCurTextColor;
356    private int mCurHintTextColor;
357    private boolean mFreezesText;
358
359    private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
360    private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
361
362    private float mShadowRadius, mShadowDx, mShadowDy;
363    private int mShadowColor;
364
365    private boolean mPreDrawRegistered;
366    private boolean mPreDrawListenerDetached;
367
368    private TextClassifier mTextClassifier;
369
370    // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is
371    // that if a user is holding down a movement key to traverse text, we shouldn't also traverse
372    // the view hierarchy. On the other hand, if the user is using the movement key to traverse
373    // views (i.e. the first movement was to traverse out of this view, or this view was traversed
374    // into by the user holding the movement key down) then we shouldn't prevent the focus from
375    // changing.
376    private boolean mPreventDefaultMovement;
377
378    private TextUtils.TruncateAt mEllipsize;
379
380    static class Drawables {
381        static final int LEFT = 0;
382        static final int TOP = 1;
383        static final int RIGHT = 2;
384        static final int BOTTOM = 3;
385
386        static final int DRAWABLE_NONE = -1;
387        static final int DRAWABLE_RIGHT = 0;
388        static final int DRAWABLE_LEFT = 1;
389
390        final Rect mCompoundRect = new Rect();
391
392        final Drawable[] mShowing = new Drawable[4];
393
394        ColorStateList mTintList;
395        PorterDuff.Mode mTintMode;
396        boolean mHasTint;
397        boolean mHasTintMode;
398
399        Drawable mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp;
400        Drawable mDrawableLeftInitial, mDrawableRightInitial;
401
402        boolean mIsRtlCompatibilityMode;
403        boolean mOverride;
404
405        int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight,
406                mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp;
407
408        int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight,
409                mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp;
410
411        int mDrawablePadding;
412
413        int mDrawableSaved = DRAWABLE_NONE;
414
415        public Drawables(Context context) {
416            final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
417            mIsRtlCompatibilityMode = targetSdkVersion < JELLY_BEAN_MR1
418                    || !context.getApplicationInfo().hasRtlSupport();
419            mOverride = false;
420        }
421
422        /**
423         * @return {@code true} if this object contains metadata that needs to
424         *         be retained, {@code false} otherwise
425         */
426        public boolean hasMetadata() {
427            return mDrawablePadding != 0 || mHasTintMode || mHasTint;
428        }
429
430        /**
431         * Updates the list of displayed drawables to account for the current
432         * layout direction.
433         *
434         * @param layoutDirection the current layout direction
435         * @return {@code true} if the displayed drawables changed
436         */
437        public boolean resolveWithLayoutDirection(int layoutDirection) {
438            final Drawable previousLeft = mShowing[Drawables.LEFT];
439            final Drawable previousRight = mShowing[Drawables.RIGHT];
440
441            // First reset "left" and "right" drawables to their initial values
442            mShowing[Drawables.LEFT] = mDrawableLeftInitial;
443            mShowing[Drawables.RIGHT] = mDrawableRightInitial;
444
445            if (mIsRtlCompatibilityMode) {
446                // Use "start" drawable as "left" drawable if the "left" drawable was not defined
447                if (mDrawableStart != null && mShowing[Drawables.LEFT] == null) {
448                    mShowing[Drawables.LEFT] = mDrawableStart;
449                    mDrawableSizeLeft = mDrawableSizeStart;
450                    mDrawableHeightLeft = mDrawableHeightStart;
451                }
452                // Use "end" drawable as "right" drawable if the "right" drawable was not defined
453                if (mDrawableEnd != null && mShowing[Drawables.RIGHT] == null) {
454                    mShowing[Drawables.RIGHT] = mDrawableEnd;
455                    mDrawableSizeRight = mDrawableSizeEnd;
456                    mDrawableHeightRight = mDrawableHeightEnd;
457                }
458            } else {
459                // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right"
460                // drawable if and only if they have been defined
461                switch(layoutDirection) {
462                    case LAYOUT_DIRECTION_RTL:
463                        if (mOverride) {
464                            mShowing[Drawables.RIGHT] = mDrawableStart;
465                            mDrawableSizeRight = mDrawableSizeStart;
466                            mDrawableHeightRight = mDrawableHeightStart;
467
468                            mShowing[Drawables.LEFT] = mDrawableEnd;
469                            mDrawableSizeLeft = mDrawableSizeEnd;
470                            mDrawableHeightLeft = mDrawableHeightEnd;
471                        }
472                        break;
473
474                    case LAYOUT_DIRECTION_LTR:
475                    default:
476                        if (mOverride) {
477                            mShowing[Drawables.LEFT] = mDrawableStart;
478                            mDrawableSizeLeft = mDrawableSizeStart;
479                            mDrawableHeightLeft = mDrawableHeightStart;
480
481                            mShowing[Drawables.RIGHT] = mDrawableEnd;
482                            mDrawableSizeRight = mDrawableSizeEnd;
483                            mDrawableHeightRight = mDrawableHeightEnd;
484                        }
485                        break;
486                }
487            }
488
489            applyErrorDrawableIfNeeded(layoutDirection);
490
491            return mShowing[Drawables.LEFT] != previousLeft
492                    || mShowing[Drawables.RIGHT] != previousRight;
493        }
494
495        public void setErrorDrawable(Drawable dr, TextView tv) {
496            if (mDrawableError != dr && mDrawableError != null) {
497                mDrawableError.setCallback(null);
498            }
499            mDrawableError = dr;
500
501            if (mDrawableError != null) {
502                final Rect compoundRect = mCompoundRect;
503                final int[] state = tv.getDrawableState();
504
505                mDrawableError.setState(state);
506                mDrawableError.copyBounds(compoundRect);
507                mDrawableError.setCallback(tv);
508                mDrawableSizeError = compoundRect.width();
509                mDrawableHeightError = compoundRect.height();
510            } else {
511                mDrawableSizeError = mDrawableHeightError = 0;
512            }
513        }
514
515        private void applyErrorDrawableIfNeeded(int layoutDirection) {
516            // first restore the initial state if needed
517            switch (mDrawableSaved) {
518                case DRAWABLE_LEFT:
519                    mShowing[Drawables.LEFT] = mDrawableTemp;
520                    mDrawableSizeLeft = mDrawableSizeTemp;
521                    mDrawableHeightLeft = mDrawableHeightTemp;
522                    break;
523                case DRAWABLE_RIGHT:
524                    mShowing[Drawables.RIGHT] = mDrawableTemp;
525                    mDrawableSizeRight = mDrawableSizeTemp;
526                    mDrawableHeightRight = mDrawableHeightTemp;
527                    break;
528                case DRAWABLE_NONE:
529                default:
530            }
531            // then, if needed, assign the Error drawable to the correct location
532            if (mDrawableError != null) {
533                switch(layoutDirection) {
534                    case LAYOUT_DIRECTION_RTL:
535                        mDrawableSaved = DRAWABLE_LEFT;
536
537                        mDrawableTemp = mShowing[Drawables.LEFT];
538                        mDrawableSizeTemp = mDrawableSizeLeft;
539                        mDrawableHeightTemp = mDrawableHeightLeft;
540
541                        mShowing[Drawables.LEFT] = mDrawableError;
542                        mDrawableSizeLeft = mDrawableSizeError;
543                        mDrawableHeightLeft = mDrawableHeightError;
544                        break;
545                    case LAYOUT_DIRECTION_LTR:
546                    default:
547                        mDrawableSaved = DRAWABLE_RIGHT;
548
549                        mDrawableTemp = mShowing[Drawables.RIGHT];
550                        mDrawableSizeTemp = mDrawableSizeRight;
551                        mDrawableHeightTemp = mDrawableHeightRight;
552
553                        mShowing[Drawables.RIGHT] = mDrawableError;
554                        mDrawableSizeRight = mDrawableSizeError;
555                        mDrawableHeightRight = mDrawableHeightError;
556                        break;
557                }
558            }
559        }
560    }
561
562    Drawables mDrawables;
563
564    private CharWrapper mCharWrapper;
565
566    private Marquee mMarquee;
567    private boolean mRestartMarquee;
568
569    private int mMarqueeRepeatLimit = 3;
570
571    private int mLastLayoutDirection = -1;
572
573    /**
574     * On some devices the fading edges add a performance penalty if used
575     * extensively in the same layout. This mode indicates how the marquee
576     * is currently being shown, if applicable. (mEllipsize will == MARQUEE)
577     */
578    private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
579
580    /**
581     * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores
582     * the layout that should be used when the mode switches.
583     */
584    private Layout mSavedMarqueeModeLayout;
585
586    @ViewDebug.ExportedProperty(category = "text")
587    private CharSequence mText;
588    private CharSequence mTransformed;
589    private BufferType mBufferType = BufferType.NORMAL;
590
591    private CharSequence mHint;
592    private Layout mHintLayout;
593
594    private MovementMethod mMovement;
595
596    private TransformationMethod mTransformation;
597    private boolean mAllowTransformationLengthChange;
598    private ChangeWatcher mChangeWatcher;
599
600    private ArrayList<TextWatcher> mListeners;
601
602    // display attributes
603    private final TextPaint mTextPaint;
604    private boolean mUserSetTextScaleX;
605    private Layout mLayout;
606    private boolean mLocalesChanged = false;
607
608    @ViewDebug.ExportedProperty(category = "text")
609    private int mGravity = Gravity.TOP | Gravity.START;
610    private boolean mHorizontallyScrolling;
611
612    private int mAutoLinkMask;
613    private boolean mLinksClickable = true;
614
615    private float mSpacingMult = 1.0f;
616    private float mSpacingAdd = 0.0f;
617
618    private int mBreakStrategy;
619    private int mHyphenationFrequency;
620    private boolean mJustify;
621
622    private int mMaximum = Integer.MAX_VALUE;
623    private int mMaxMode = LINES;
624    private int mMinimum = 0;
625    private int mMinMode = LINES;
626
627    private int mOldMaximum = mMaximum;
628    private int mOldMaxMode = mMaxMode;
629
630    private int mMaxWidth = Integer.MAX_VALUE;
631    private int mMaxWidthMode = PIXELS;
632    private int mMinWidth = 0;
633    private int mMinWidthMode = PIXELS;
634
635    private boolean mSingleLine;
636    private int mDesiredHeightAtMeasure = -1;
637    private boolean mIncludePad = true;
638    private int mDeferScroll = -1;
639
640    // tmp primitives, so we don't alloc them on each draw
641    private Rect mTempRect;
642    private long mLastScroll;
643    private Scroller mScroller;
644    private TextPaint mTempTextPaint;
645
646    private BoringLayout.Metrics mBoring, mHintBoring;
647    private BoringLayout mSavedLayout, mSavedHintLayout;
648
649    private TextDirectionHeuristic mTextDir;
650
651    private InputFilter[] mFilters = NO_FILTERS;
652
653    private volatile Locale mCurrentSpellCheckerLocaleCache;
654
655    // It is possible to have a selection even when mEditor is null (programmatically set, like when
656    // a link is pressed). These highlight-related fields do not go in mEditor.
657    int mHighlightColor = 0x6633B5E5;
658    private Path mHighlightPath;
659    private final Paint mHighlightPaint;
660    private boolean mHighlightPathBogus = true;
661
662    // Although these fields are specific to editable text, they are not added to Editor because
663    // they are defined by the TextView's style and are theme-dependent.
664    int mCursorDrawableRes;
665    // These six fields, could be moved to Editor, since we know their default values and we
666    // could condition the creation of the Editor to a non standard value. This is however
667    // brittle since the hardcoded values here (such as
668    // com.android.internal.R.drawable.text_select_handle_left) would have to be updated if the
669    // default style is modified.
670    int mTextSelectHandleLeftRes;
671    int mTextSelectHandleRightRes;
672    int mTextSelectHandleRes;
673    int mTextEditSuggestionItemLayout;
674    int mTextEditSuggestionContainerLayout;
675    int mTextEditSuggestionHighlightStyle;
676
677    /**
678     * EditText specific data, created on demand when one of the Editor fields is used.
679     * See {@link #createEditorIfNeeded()}.
680     */
681    private Editor mEditor;
682
683    private static final int DEVICE_PROVISIONED_UNKNOWN = 0;
684    private static final int DEVICE_PROVISIONED_NO = 1;
685    private static final int DEVICE_PROVISIONED_YES = 2;
686
687    /**
688     * Some special options such as sharing selected text should only be shown if the device
689     * is provisioned. Only check the provisioned state once for a given view instance.
690     */
691    private int mDeviceProvisionedState = DEVICE_PROVISIONED_UNKNOWN;
692
693    /**
694     * The TextView does not auto-size text (default).
695     */
696    public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0;
697
698    /**
699     * The TextView scales text size both horizontally and vertically to fit within the
700     * container.
701     */
702    public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1;
703
704    /** @hide */
705    @IntDef({AUTO_SIZE_TEXT_TYPE_NONE, AUTO_SIZE_TEXT_TYPE_UNIFORM})
706    @Retention(RetentionPolicy.SOURCE)
707    public @interface AutoSizeTextType {}
708    // Default minimum size for auto-sizing text in scaled pixels.
709    private static final int DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP = 12;
710    // Default maximum size for auto-sizing text in scaled pixels.
711    private static final int DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP = 112;
712    // Default value for the step size in pixels.
713    private static final int DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX = 1;
714    // Use this to specify that any of the auto-size configuration int values have not been set.
715    private static final int UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE = -1;
716    // Auto-size text type.
717    private int mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
718    // Specify if auto-size text is needed.
719    private boolean mNeedsAutoSizeText = false;
720    // Step size for auto-sizing in pixels.
721    private int mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
722    // Minimum text size for auto-sizing in pixels.
723    private int mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
724    // Maximum text size for auto-sizing in pixels.
725    private int mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
726    // Contains a (specified or computed) distinct sorted set of text sizes in pixels to pick from
727    // when auto-sizing text.
728    private int[] mAutoSizeTextSizesInPx = EmptyArray.INT;
729    // Specifies whether auto-size should use the provided auto size steps set or if it should
730    // build the steps set using mAutoSizeMinTextSizeInPx, mAutoSizeMaxTextSizeInPx and
731    // mAutoSizeStepGranularityInPx.
732    private boolean mHasPresetAutoSizeValues = false;
733
734    // Indicates whether the text was set from resources or dynamically, so it can be used to
735    // sanitize autofill requests.
736    private boolean mTextFromResource = false;
737
738    /**
739     * Kick-start the font cache for the zygote process (to pay the cost of
740     * initializing freetype for our default font only once).
741     * @hide
742     */
743    public static void preloadFontCache() {
744        Paint p = new Paint();
745        p.setAntiAlias(true);
746        // Ensure that the Typeface is loaded here.
747        // Typically, Typeface is preloaded by zygote but not on all devices, e.g. Android Auto.
748        // So, sets Typeface.DEFAULT explicitly here for ensuring that the Typeface is loaded here
749        // since Paint.measureText can not be called without Typeface static initializer.
750        p.setTypeface(Typeface.DEFAULT);
751        // We don't care about the result, just the side-effect of measuring.
752        p.measureText("H");
753    }
754
755    /**
756     * Interface definition for a callback to be invoked when an action is
757     * performed on the editor.
758     */
759    public interface OnEditorActionListener {
760        /**
761         * Called when an action is being performed.
762         *
763         * @param v The view that was clicked.
764         * @param actionId Identifier of the action.  This will be either the
765         * identifier you supplied, or {@link EditorInfo#IME_NULL
766         * EditorInfo.IME_NULL} if being called due to the enter key
767         * being pressed.
768         * @param event If triggered by an enter key, this is the event;
769         * otherwise, this is null.
770         * @return Return true if you have consumed the action, else false.
771         */
772        boolean onEditorAction(TextView v, int actionId, KeyEvent event);
773    }
774
775    public TextView(Context context) {
776        this(context, null);
777    }
778
779    public TextView(Context context, @Nullable AttributeSet attrs) {
780        this(context, attrs, com.android.internal.R.attr.textViewStyle);
781    }
782
783    public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
784        this(context, attrs, defStyleAttr, 0);
785    }
786
787    @SuppressWarnings("deprecation")
788    public TextView(
789            Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
790        super(context, attrs, defStyleAttr, defStyleRes);
791
792        // TextView is important by default, unless app developer overrode attribute.
793        if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
794            setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
795        }
796
797        mText = "";
798
799        final Resources res = getResources();
800        final CompatibilityInfo compat = res.getCompatibilityInfo();
801
802        mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
803        mTextPaint.density = res.getDisplayMetrics().density;
804        mTextPaint.setCompatibilityScaling(compat.applicationScale);
805
806        mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
807        mHighlightPaint.setCompatibilityScaling(compat.applicationScale);
808
809        mMovement = getDefaultMovementMethod();
810
811        mTransformation = null;
812
813        int textColorHighlight = 0;
814        ColorStateList textColor = null;
815        ColorStateList textColorHint = null;
816        ColorStateList textColorLink = null;
817        int textSize = 15;
818        String fontFamily = null;
819        Typeface fontTypeface = null;
820        boolean fontFamilyExplicit = false;
821        int typefaceIndex = -1;
822        int styleIndex = -1;
823        boolean allCaps = false;
824        int shadowcolor = 0;
825        float dx = 0, dy = 0, r = 0;
826        boolean elegant = false;
827        float letterSpacing = 0;
828        String fontFeatureSettings = null;
829        mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
830        mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
831        mJustify = false;
832
833        final Resources.Theme theme = context.getTheme();
834
835        /*
836         * Look the appearance up without checking first if it exists because
837         * almost every TextView has one and it greatly simplifies the logic
838         * to be able to parse the appearance first and then let specific tags
839         * for this View override it.
840         */
841        TypedArray a = theme.obtainStyledAttributes(attrs,
842                com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes);
843        TypedArray appearance = null;
844        int ap = a.getResourceId(
845                com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);
846        a.recycle();
847        if (ap != -1) {
848            appearance = theme.obtainStyledAttributes(
849                    ap, com.android.internal.R.styleable.TextAppearance);
850        }
851        if (appearance != null) {
852            int n = appearance.getIndexCount();
853            for (int i = 0; i < n; i++) {
854                int attr = appearance.getIndex(i);
855
856                switch (attr) {
857                    case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
858                        textColorHighlight = appearance.getColor(attr, textColorHighlight);
859                        break;
860
861                    case com.android.internal.R.styleable.TextAppearance_textColor:
862                        textColor = appearance.getColorStateList(attr);
863                        break;
864
865                    case com.android.internal.R.styleable.TextAppearance_textColorHint:
866                        textColorHint = appearance.getColorStateList(attr);
867                        break;
868
869                    case com.android.internal.R.styleable.TextAppearance_textColorLink:
870                        textColorLink = appearance.getColorStateList(attr);
871                        break;
872
873                    case com.android.internal.R.styleable.TextAppearance_textSize:
874                        textSize = appearance.getDimensionPixelSize(attr, textSize);
875                        break;
876
877                    case com.android.internal.R.styleable.TextAppearance_typeface:
878                        typefaceIndex = appearance.getInt(attr, -1);
879                        break;
880
881                    case com.android.internal.R.styleable.TextAppearance_fontFamily:
882                        try {
883                            fontTypeface = appearance.getFont(attr);
884                        } catch (UnsupportedOperationException | Resources.NotFoundException e) {
885                            // Expected if it is not a font resource.
886                        }
887                        if (fontTypeface == null) {
888                            fontFamily = appearance.getString(attr);
889                        }
890                        break;
891
892                    case com.android.internal.R.styleable.TextAppearance_textStyle:
893                        styleIndex = appearance.getInt(attr, -1);
894                        break;
895
896                    case com.android.internal.R.styleable.TextAppearance_textAllCaps:
897                        allCaps = appearance.getBoolean(attr, false);
898                        break;
899
900                    case com.android.internal.R.styleable.TextAppearance_shadowColor:
901                        shadowcolor = appearance.getInt(attr, 0);
902                        break;
903
904                    case com.android.internal.R.styleable.TextAppearance_shadowDx:
905                        dx = appearance.getFloat(attr, 0);
906                        break;
907
908                    case com.android.internal.R.styleable.TextAppearance_shadowDy:
909                        dy = appearance.getFloat(attr, 0);
910                        break;
911
912                    case com.android.internal.R.styleable.TextAppearance_shadowRadius:
913                        r = appearance.getFloat(attr, 0);
914                        break;
915
916                    case com.android.internal.R.styleable.TextAppearance_elegantTextHeight:
917                        elegant = appearance.getBoolean(attr, false);
918                        break;
919
920                    case com.android.internal.R.styleable.TextAppearance_letterSpacing:
921                        letterSpacing = appearance.getFloat(attr, 0);
922                        break;
923
924                    case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings:
925                        fontFeatureSettings = appearance.getString(attr);
926                        break;
927                }
928            }
929
930            appearance.recycle();
931        }
932
933        boolean editable = getDefaultEditable();
934        CharSequence inputMethod = null;
935        int numeric = 0;
936        CharSequence digits = null;
937        boolean phone = false;
938        boolean autotext = false;
939        int autocap = -1;
940        int buffertype = 0;
941        boolean selectallonfocus = false;
942        Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
943                drawableBottom = null, drawableStart = null, drawableEnd = null;
944        ColorStateList drawableTint = null;
945        PorterDuff.Mode drawableTintMode = null;
946        int drawablePadding = 0;
947        int ellipsize = -1;
948        boolean singleLine = false;
949        int maxlength = -1;
950        CharSequence text = "";
951        CharSequence hint = null;
952        boolean password = false;
953        int autoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
954        int autoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
955        int autoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
956        int inputType = EditorInfo.TYPE_NULL;
957        a = theme.obtainStyledAttributes(
958                    attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
959
960        int n = a.getIndexCount();
961
962        boolean fromResourceId = false;
963        for (int i = 0; i < n; i++) {
964            int attr = a.getIndex(i);
965
966            switch (attr) {
967                case com.android.internal.R.styleable.TextView_editable:
968                    editable = a.getBoolean(attr, editable);
969                    break;
970
971                case com.android.internal.R.styleable.TextView_inputMethod:
972                    inputMethod = a.getText(attr);
973                    break;
974
975                case com.android.internal.R.styleable.TextView_numeric:
976                    numeric = a.getInt(attr, numeric);
977                    break;
978
979                case com.android.internal.R.styleable.TextView_digits:
980                    digits = a.getText(attr);
981                    break;
982
983                case com.android.internal.R.styleable.TextView_phoneNumber:
984                    phone = a.getBoolean(attr, phone);
985                    break;
986
987                case com.android.internal.R.styleable.TextView_autoText:
988                    autotext = a.getBoolean(attr, autotext);
989                    break;
990
991                case com.android.internal.R.styleable.TextView_capitalize:
992                    autocap = a.getInt(attr, autocap);
993                    break;
994
995                case com.android.internal.R.styleable.TextView_bufferType:
996                    buffertype = a.getInt(attr, buffertype);
997                    break;
998
999                case com.android.internal.R.styleable.TextView_selectAllOnFocus:
1000                    selectallonfocus = a.getBoolean(attr, selectallonfocus);
1001                    break;
1002
1003                case com.android.internal.R.styleable.TextView_autoLink:
1004                    mAutoLinkMask = a.getInt(attr, 0);
1005                    break;
1006
1007                case com.android.internal.R.styleable.TextView_linksClickable:
1008                    mLinksClickable = a.getBoolean(attr, true);
1009                    break;
1010
1011                case com.android.internal.R.styleable.TextView_drawableLeft:
1012                    drawableLeft = a.getDrawable(attr);
1013                    break;
1014
1015                case com.android.internal.R.styleable.TextView_drawableTop:
1016                    drawableTop = a.getDrawable(attr);
1017                    break;
1018
1019                case com.android.internal.R.styleable.TextView_drawableRight:
1020                    drawableRight = a.getDrawable(attr);
1021                    break;
1022
1023                case com.android.internal.R.styleable.TextView_drawableBottom:
1024                    drawableBottom = a.getDrawable(attr);
1025                    break;
1026
1027                case com.android.internal.R.styleable.TextView_drawableStart:
1028                    drawableStart = a.getDrawable(attr);
1029                    break;
1030
1031                case com.android.internal.R.styleable.TextView_drawableEnd:
1032                    drawableEnd = a.getDrawable(attr);
1033                    break;
1034
1035                case com.android.internal.R.styleable.TextView_drawableTint:
1036                    drawableTint = a.getColorStateList(attr);
1037                    break;
1038
1039                case com.android.internal.R.styleable.TextView_drawableTintMode:
1040                    drawableTintMode = Drawable.parseTintMode(a.getInt(attr, -1), drawableTintMode);
1041                    break;
1042
1043                case com.android.internal.R.styleable.TextView_drawablePadding:
1044                    drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
1045                    break;
1046
1047                case com.android.internal.R.styleable.TextView_maxLines:
1048                    setMaxLines(a.getInt(attr, -1));
1049                    break;
1050
1051                case com.android.internal.R.styleable.TextView_maxHeight:
1052                    setMaxHeight(a.getDimensionPixelSize(attr, -1));
1053                    break;
1054
1055                case com.android.internal.R.styleable.TextView_lines:
1056                    setLines(a.getInt(attr, -1));
1057                    break;
1058
1059                case com.android.internal.R.styleable.TextView_height:
1060                    setHeight(a.getDimensionPixelSize(attr, -1));
1061                    break;
1062
1063                case com.android.internal.R.styleable.TextView_minLines:
1064                    setMinLines(a.getInt(attr, -1));
1065                    break;
1066
1067                case com.android.internal.R.styleable.TextView_minHeight:
1068                    setMinHeight(a.getDimensionPixelSize(attr, -1));
1069                    break;
1070
1071                case com.android.internal.R.styleable.TextView_maxEms:
1072                    setMaxEms(a.getInt(attr, -1));
1073                    break;
1074
1075                case com.android.internal.R.styleable.TextView_maxWidth:
1076                    setMaxWidth(a.getDimensionPixelSize(attr, -1));
1077                    break;
1078
1079                case com.android.internal.R.styleable.TextView_ems:
1080                    setEms(a.getInt(attr, -1));
1081                    break;
1082
1083                case com.android.internal.R.styleable.TextView_width:
1084                    setWidth(a.getDimensionPixelSize(attr, -1));
1085                    break;
1086
1087                case com.android.internal.R.styleable.TextView_minEms:
1088                    setMinEms(a.getInt(attr, -1));
1089                    break;
1090
1091                case com.android.internal.R.styleable.TextView_minWidth:
1092                    setMinWidth(a.getDimensionPixelSize(attr, -1));
1093                    break;
1094
1095                case com.android.internal.R.styleable.TextView_gravity:
1096                    setGravity(a.getInt(attr, -1));
1097                    break;
1098
1099                case com.android.internal.R.styleable.TextView_hint:
1100                    hint = a.getText(attr);
1101                    break;
1102
1103                case com.android.internal.R.styleable.TextView_text:
1104                    fromResourceId = true;
1105                    text = a.getText(attr);
1106                    break;
1107
1108                case com.android.internal.R.styleable.TextView_scrollHorizontally:
1109                    if (a.getBoolean(attr, false)) {
1110                        setHorizontallyScrolling(true);
1111                    }
1112                    break;
1113
1114                case com.android.internal.R.styleable.TextView_singleLine:
1115                    singleLine = a.getBoolean(attr, singleLine);
1116                    break;
1117
1118                case com.android.internal.R.styleable.TextView_ellipsize:
1119                    ellipsize = a.getInt(attr, ellipsize);
1120                    break;
1121
1122                case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
1123                    setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
1124                    break;
1125
1126                case com.android.internal.R.styleable.TextView_includeFontPadding:
1127                    if (!a.getBoolean(attr, true)) {
1128                        setIncludeFontPadding(false);
1129                    }
1130                    break;
1131
1132                case com.android.internal.R.styleable.TextView_cursorVisible:
1133                    if (!a.getBoolean(attr, true)) {
1134                        setCursorVisible(false);
1135                    }
1136                    break;
1137
1138                case com.android.internal.R.styleable.TextView_maxLength:
1139                    maxlength = a.getInt(attr, -1);
1140                    break;
1141
1142                case com.android.internal.R.styleable.TextView_textScaleX:
1143                    setTextScaleX(a.getFloat(attr, 1.0f));
1144                    break;
1145
1146                case com.android.internal.R.styleable.TextView_freezesText:
1147                    mFreezesText = a.getBoolean(attr, false);
1148                    break;
1149
1150                case com.android.internal.R.styleable.TextView_shadowColor:
1151                    shadowcolor = a.getInt(attr, 0);
1152                    break;
1153
1154                case com.android.internal.R.styleable.TextView_shadowDx:
1155                    dx = a.getFloat(attr, 0);
1156                    break;
1157
1158                case com.android.internal.R.styleable.TextView_shadowDy:
1159                    dy = a.getFloat(attr, 0);
1160                    break;
1161
1162                case com.android.internal.R.styleable.TextView_shadowRadius:
1163                    r = a.getFloat(attr, 0);
1164                    break;
1165
1166                case com.android.internal.R.styleable.TextView_enabled:
1167                    setEnabled(a.getBoolean(attr, isEnabled()));
1168                    break;
1169
1170                case com.android.internal.R.styleable.TextView_textColorHighlight:
1171                    textColorHighlight = a.getColor(attr, textColorHighlight);
1172                    break;
1173
1174                case com.android.internal.R.styleable.TextView_textColor:
1175                    textColor = a.getColorStateList(attr);
1176                    break;
1177
1178                case com.android.internal.R.styleable.TextView_textColorHint:
1179                    textColorHint = a.getColorStateList(attr);
1180                    break;
1181
1182                case com.android.internal.R.styleable.TextView_textColorLink:
1183                    textColorLink = a.getColorStateList(attr);
1184                    break;
1185
1186                case com.android.internal.R.styleable.TextView_textSize:
1187                    textSize = a.getDimensionPixelSize(attr, textSize);
1188                    break;
1189
1190                case com.android.internal.R.styleable.TextView_typeface:
1191                    typefaceIndex = a.getInt(attr, typefaceIndex);
1192                    break;
1193
1194                case com.android.internal.R.styleable.TextView_textStyle:
1195                    styleIndex = a.getInt(attr, styleIndex);
1196                    break;
1197
1198                case com.android.internal.R.styleable.TextView_fontFamily:
1199                    try {
1200                        fontTypeface = a.getFont(attr);
1201                    } catch (UnsupportedOperationException | Resources.NotFoundException e) {
1202                        // Expected if it is not a resource reference or if it is a reference to
1203                        // another resource type.
1204                    }
1205                    if (fontTypeface == null) {
1206                        fontFamily = a.getString(attr);
1207                    }
1208                    fontFamilyExplicit = true;
1209                    break;
1210
1211                case com.android.internal.R.styleable.TextView_password:
1212                    password = a.getBoolean(attr, password);
1213                    break;
1214
1215                case com.android.internal.R.styleable.TextView_lineSpacingExtra:
1216                    mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
1217                    break;
1218
1219                case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
1220                    mSpacingMult = a.getFloat(attr, mSpacingMult);
1221                    break;
1222
1223                case com.android.internal.R.styleable.TextView_inputType:
1224                    inputType = a.getInt(attr, EditorInfo.TYPE_NULL);
1225                    break;
1226
1227                case com.android.internal.R.styleable.TextView_allowUndo:
1228                    createEditorIfNeeded();
1229                    mEditor.mAllowUndo = a.getBoolean(attr, true);
1230                    break;
1231
1232                case com.android.internal.R.styleable.TextView_imeOptions:
1233                    createEditorIfNeeded();
1234                    mEditor.createInputContentTypeIfNeeded();
1235                    mEditor.mInputContentType.imeOptions = a.getInt(attr,
1236                            mEditor.mInputContentType.imeOptions);
1237                    break;
1238
1239                case com.android.internal.R.styleable.TextView_imeActionLabel:
1240                    createEditorIfNeeded();
1241                    mEditor.createInputContentTypeIfNeeded();
1242                    mEditor.mInputContentType.imeActionLabel = a.getText(attr);
1243                    break;
1244
1245                case com.android.internal.R.styleable.TextView_imeActionId:
1246                    createEditorIfNeeded();
1247                    mEditor.createInputContentTypeIfNeeded();
1248                    mEditor.mInputContentType.imeActionId = a.getInt(attr,
1249                            mEditor.mInputContentType.imeActionId);
1250                    break;
1251
1252                case com.android.internal.R.styleable.TextView_privateImeOptions:
1253                    setPrivateImeOptions(a.getString(attr));
1254                    break;
1255
1256                case com.android.internal.R.styleable.TextView_editorExtras:
1257                    try {
1258                        setInputExtras(a.getResourceId(attr, 0));
1259                    } catch (XmlPullParserException e) {
1260                        Log.w(LOG_TAG, "Failure reading input extras", e);
1261                    } catch (IOException e) {
1262                        Log.w(LOG_TAG, "Failure reading input extras", e);
1263                    }
1264                    break;
1265
1266                case com.android.internal.R.styleable.TextView_textCursorDrawable:
1267                    mCursorDrawableRes = a.getResourceId(attr, 0);
1268                    break;
1269
1270                case com.android.internal.R.styleable.TextView_textSelectHandleLeft:
1271                    mTextSelectHandleLeftRes = a.getResourceId(attr, 0);
1272                    break;
1273
1274                case com.android.internal.R.styleable.TextView_textSelectHandleRight:
1275                    mTextSelectHandleRightRes = a.getResourceId(attr, 0);
1276                    break;
1277
1278                case com.android.internal.R.styleable.TextView_textSelectHandle:
1279                    mTextSelectHandleRes = a.getResourceId(attr, 0);
1280                    break;
1281
1282                case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout:
1283                    mTextEditSuggestionItemLayout = a.getResourceId(attr, 0);
1284                    break;
1285
1286                case com.android.internal.R.styleable.TextView_textEditSuggestionContainerLayout:
1287                    mTextEditSuggestionContainerLayout = a.getResourceId(attr, 0);
1288                    break;
1289
1290                case com.android.internal.R.styleable.TextView_textEditSuggestionHighlightStyle:
1291                    mTextEditSuggestionHighlightStyle = a.getResourceId(attr, 0);
1292                    break;
1293
1294                case com.android.internal.R.styleable.TextView_textIsSelectable:
1295                    setTextIsSelectable(a.getBoolean(attr, false));
1296                    break;
1297
1298                case com.android.internal.R.styleable.TextView_textAllCaps:
1299                    allCaps = a.getBoolean(attr, false);
1300                    break;
1301
1302                case com.android.internal.R.styleable.TextView_elegantTextHeight:
1303                    elegant = a.getBoolean(attr, false);
1304                    break;
1305
1306                case com.android.internal.R.styleable.TextView_letterSpacing:
1307                    letterSpacing = a.getFloat(attr, 0);
1308                    break;
1309
1310                case com.android.internal.R.styleable.TextView_fontFeatureSettings:
1311                    fontFeatureSettings = a.getString(attr);
1312                    break;
1313
1314                case com.android.internal.R.styleable.TextView_breakStrategy:
1315                    mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE);
1316                    break;
1317
1318                case com.android.internal.R.styleable.TextView_hyphenationFrequency:
1319                    mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE);
1320                    break;
1321
1322                case com.android.internal.R.styleable.TextView_autoSizeTextType:
1323                    mAutoSizeTextType = a.getInt(attr, AUTO_SIZE_TEXT_TYPE_NONE);
1324                    break;
1325
1326                case com.android.internal.R.styleable.TextView_autoSizeStepGranularity:
1327                    autoSizeStepGranularityInPx = a.getDimensionPixelSize(attr,
1328                        UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1329                    break;
1330
1331                case com.android.internal.R.styleable.TextView_autoSizeMinTextSize:
1332                    autoSizeMinTextSizeInPx = a.getDimensionPixelSize(attr,
1333                        UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1334                    break;
1335
1336                case com.android.internal.R.styleable.TextView_autoSizeMaxTextSize:
1337                    autoSizeMaxTextSizeInPx = a.getDimensionPixelSize(attr,
1338                        UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1339                    break;
1340
1341                case com.android.internal.R.styleable.TextView_autoSizePresetSizes:
1342                    final int autoSizeStepSizeArrayResId = a.getResourceId(attr, 0);
1343                    if (autoSizeStepSizeArrayResId > 0) {
1344                        final TypedArray autoSizePreDefTextSizes = a.getResources()
1345                                .obtainTypedArray(autoSizeStepSizeArrayResId);
1346                        setupAutoSizeUniformPresetSizes(autoSizePreDefTextSizes);
1347                        autoSizePreDefTextSizes.recycle();
1348                    }
1349                    break;
1350            }
1351        }
1352
1353        a.recycle();
1354
1355        BufferType bufferType = BufferType.EDITABLE;
1356
1357        final int variation =
1358                inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
1359        final boolean passwordInputType = variation
1360                == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
1361        final boolean webPasswordInputType = variation
1362                == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD);
1363        final boolean numberPasswordInputType = variation
1364                == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
1365
1366        if (inputMethod != null) {
1367            Class<?> c;
1368
1369            try {
1370                c = Class.forName(inputMethod.toString());
1371            } catch (ClassNotFoundException ex) {
1372                throw new RuntimeException(ex);
1373            }
1374
1375            try {
1376                createEditorIfNeeded();
1377                mEditor.mKeyListener = (KeyListener) c.newInstance();
1378            } catch (InstantiationException ex) {
1379                throw new RuntimeException(ex);
1380            } catch (IllegalAccessException ex) {
1381                throw new RuntimeException(ex);
1382            }
1383            try {
1384                mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1385                        ? inputType
1386                        : mEditor.mKeyListener.getInputType();
1387            } catch (IncompatibleClassChangeError e) {
1388                mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1389            }
1390        } else if (digits != null) {
1391            createEditorIfNeeded();
1392            mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString());
1393            // If no input type was specified, we will default to generic
1394            // text, since we can't tell the IME about the set of digits
1395            // that was selected.
1396            mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1397                    ? inputType : EditorInfo.TYPE_CLASS_TEXT;
1398        } else if (inputType != EditorInfo.TYPE_NULL) {
1399            setInputType(inputType, true);
1400            // If set, the input type overrides what was set using the deprecated singleLine flag.
1401            singleLine = !isMultilineInputType(inputType);
1402        } else if (phone) {
1403            createEditorIfNeeded();
1404            mEditor.mKeyListener = DialerKeyListener.getInstance();
1405            mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
1406        } else if (numeric != 0) {
1407            createEditorIfNeeded();
1408            mEditor.mKeyListener = DigitsKeyListener.getInstance((numeric & SIGNED) != 0,
1409                                                   (numeric & DECIMAL) != 0);
1410            inputType = EditorInfo.TYPE_CLASS_NUMBER;
1411            if ((numeric & SIGNED) != 0) {
1412                inputType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED;
1413            }
1414            if ((numeric & DECIMAL) != 0) {
1415                inputType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL;
1416            }
1417            mEditor.mInputType = inputType;
1418        } else if (autotext || autocap != -1) {
1419            TextKeyListener.Capitalize cap;
1420
1421            inputType = EditorInfo.TYPE_CLASS_TEXT;
1422
1423            switch (autocap) {
1424                case 1:
1425                    cap = TextKeyListener.Capitalize.SENTENCES;
1426                    inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
1427                    break;
1428
1429                case 2:
1430                    cap = TextKeyListener.Capitalize.WORDS;
1431                    inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
1432                    break;
1433
1434                case 3:
1435                    cap = TextKeyListener.Capitalize.CHARACTERS;
1436                    inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
1437                    break;
1438
1439                default:
1440                    cap = TextKeyListener.Capitalize.NONE;
1441                    break;
1442            }
1443
1444            createEditorIfNeeded();
1445            mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap);
1446            mEditor.mInputType = inputType;
1447        } else if (editable) {
1448            createEditorIfNeeded();
1449            mEditor.mKeyListener = TextKeyListener.getInstance();
1450            mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1451        } else if (isTextSelectable()) {
1452            // Prevent text changes from keyboard.
1453            if (mEditor != null) {
1454                mEditor.mKeyListener = null;
1455                mEditor.mInputType = EditorInfo.TYPE_NULL;
1456            }
1457            bufferType = BufferType.SPANNABLE;
1458            // So that selection can be changed using arrow keys and touch is handled.
1459            setMovementMethod(ArrowKeyMovementMethod.getInstance());
1460        } else {
1461            if (mEditor != null) mEditor.mKeyListener = null;
1462
1463            switch (buffertype) {
1464                case 0:
1465                    bufferType = BufferType.NORMAL;
1466                    break;
1467                case 1:
1468                    bufferType = BufferType.SPANNABLE;
1469                    break;
1470                case 2:
1471                    bufferType = BufferType.EDITABLE;
1472                    break;
1473            }
1474        }
1475
1476        if (mEditor != null) {
1477            mEditor.adjustInputType(password, passwordInputType, webPasswordInputType,
1478                    numberPasswordInputType);
1479        }
1480
1481        if (selectallonfocus) {
1482            createEditorIfNeeded();
1483            mEditor.mSelectAllOnFocus = true;
1484
1485            if (bufferType == BufferType.NORMAL) {
1486                bufferType = BufferType.SPANNABLE;
1487            }
1488        }
1489
1490        // Set up the tint (if needed) before setting the drawables so that it
1491        // gets applied correctly.
1492        if (drawableTint != null || drawableTintMode != null) {
1493            if (mDrawables == null) {
1494                mDrawables = new Drawables(context);
1495            }
1496            if (drawableTint != null) {
1497                mDrawables.mTintList = drawableTint;
1498                mDrawables.mHasTint = true;
1499            }
1500            if (drawableTintMode != null) {
1501                mDrawables.mTintMode = drawableTintMode;
1502                mDrawables.mHasTintMode = true;
1503            }
1504        }
1505
1506        // This call will save the initial left/right drawables
1507        setCompoundDrawablesWithIntrinsicBounds(
1508                drawableLeft, drawableTop, drawableRight, drawableBottom);
1509        setRelativeDrawablesIfNeeded(drawableStart, drawableEnd);
1510        setCompoundDrawablePadding(drawablePadding);
1511
1512        // Same as setSingleLine(), but make sure the transformation method and the maximum number
1513        // of lines of height are unchanged for multi-line TextViews.
1514        setInputTypeSingleLine(singleLine);
1515        applySingleLine(singleLine, singleLine, singleLine);
1516
1517        if (singleLine && getKeyListener() == null && ellipsize < 0) {
1518            ellipsize = 3; // END
1519        }
1520
1521        switch (ellipsize) {
1522            case 1:
1523                setEllipsize(TextUtils.TruncateAt.START);
1524                break;
1525            case 2:
1526                setEllipsize(TextUtils.TruncateAt.MIDDLE);
1527                break;
1528            case 3:
1529                setEllipsize(TextUtils.TruncateAt.END);
1530                break;
1531            case 4:
1532                if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) {
1533                    setHorizontalFadingEdgeEnabled(true);
1534                    mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
1535                } else {
1536                    setHorizontalFadingEdgeEnabled(false);
1537                    mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
1538                }
1539                setEllipsize(TextUtils.TruncateAt.MARQUEE);
1540                break;
1541        }
1542
1543        setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000));
1544        setHintTextColor(textColorHint);
1545        setLinkTextColor(textColorLink);
1546        if (textColorHighlight != 0) {
1547            setHighlightColor(textColorHighlight);
1548        }
1549        setRawTextSize(textSize);
1550        setElegantTextHeight(elegant);
1551        setLetterSpacing(letterSpacing);
1552        setFontFeatureSettings(fontFeatureSettings);
1553
1554        if (allCaps) {
1555            setTransformationMethod(new AllCapsTransformationMethod(getContext()));
1556        }
1557
1558        if (password || passwordInputType || webPasswordInputType || numberPasswordInputType) {
1559            setTransformationMethod(PasswordTransformationMethod.getInstance());
1560            typefaceIndex = MONOSPACE;
1561        } else if (mEditor != null
1562                && (mEditor.mInputType
1563                        & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
1564                == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
1565            typefaceIndex = MONOSPACE;
1566        }
1567
1568        if (typefaceIndex != -1 && !fontFamilyExplicit) {
1569            fontFamily = null;
1570        }
1571        setTypefaceFromAttrs(fontTypeface, fontFamily, typefaceIndex, styleIndex);
1572
1573        if (shadowcolor != 0) {
1574            setShadowLayer(r, dx, dy, shadowcolor);
1575        }
1576
1577        if (maxlength >= 0) {
1578            setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
1579        } else {
1580            setFilters(NO_FILTERS);
1581        }
1582
1583        setText(text, bufferType);
1584        if (fromResourceId) {
1585            mTextFromResource = true;
1586        }
1587
1588        if (hint != null) setHint(hint);
1589
1590        /*
1591         * Views are not normally clickable unless specified to be.
1592         * However, TextViews that have input or movement methods *are*
1593         * clickable by default. By setting clickable here, we implicitly set focusable as well
1594         * if not overridden by the developer.
1595         */
1596        a = context.obtainStyledAttributes(
1597                attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
1598        boolean canInputOrMove = (mMovement != null || getKeyListener() != null);
1599        boolean clickable = canInputOrMove || isClickable();
1600        boolean longClickable = canInputOrMove || isLongClickable();
1601
1602        n = a.getIndexCount();
1603        for (int i = 0; i < n; i++) {
1604            int attr = a.getIndex(i);
1605
1606            switch (attr) {
1607                case com.android.internal.R.styleable.View_clickable:
1608                    clickable = a.getBoolean(attr, clickable);
1609                    break;
1610
1611                case com.android.internal.R.styleable.View_longClickable:
1612                    longClickable = a.getBoolean(attr, longClickable);
1613                    break;
1614            }
1615        }
1616        a.recycle();
1617
1618        setClickable(clickable);
1619        setLongClickable(longClickable);
1620
1621        if (mEditor != null) mEditor.prepareCursorControllers();
1622
1623        // If not explicitly specified this view is important for accessibility.
1624        if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
1625            setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
1626        }
1627
1628        if (supportsAutoSizeText()) {
1629            if (mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) {
1630                // If uniform auto-size has been specified but preset values have not been set then
1631                // replace the auto-size configuration values that have not been specified with the
1632                // defaults.
1633                if (!mHasPresetAutoSizeValues) {
1634                    final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1635
1636                    if (autoSizeMinTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1637                        autoSizeMinTextSizeInPx = (int) TypedValue.applyDimension(
1638                                TypedValue.COMPLEX_UNIT_SP,
1639                                DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP,
1640                                displayMetrics);
1641                    }
1642
1643                    if (autoSizeMaxTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1644                        autoSizeMaxTextSizeInPx = (int) TypedValue.applyDimension(
1645                                TypedValue.COMPLEX_UNIT_SP,
1646                                DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP,
1647                                displayMetrics);
1648                    }
1649
1650                    if (autoSizeMinTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1651                        autoSizeStepGranularityInPx = DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX;
1652                    }
1653
1654                    validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx,
1655                            autoSizeMaxTextSizeInPx,
1656                            autoSizeStepGranularityInPx);
1657                }
1658
1659                setupAutoSizeText();
1660            }
1661        } else {
1662            mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
1663        }
1664    }
1665
1666    /**
1667     * Specify whether this widget should automatically scale the text to try to perfectly fit
1668     * within the layout bounds by using the default auto-size configuration.
1669     *
1670     * @param autoSizeTextType the type of auto-size. Must be one of
1671     *        {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or
1672     *        {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM}
1673     *
1674     * @attr ref android.R.styleable#TextView_autoSizeTextType
1675     *
1676     * @see #getAutoSizeTextType()
1677     */
1678    public void setAutoSizeTextTypeWithDefaults(@AutoSizeTextType int autoSizeTextType) {
1679        if (supportsAutoSizeText()) {
1680            switch (autoSizeTextType) {
1681                case AUTO_SIZE_TEXT_TYPE_NONE:
1682                    clearAutoSizeConfiguration();
1683                    break;
1684                case AUTO_SIZE_TEXT_TYPE_UNIFORM:
1685                    final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1686                    final int autoSizeMinTextSizeInPx = (int) TypedValue.applyDimension(
1687                            TypedValue.COMPLEX_UNIT_SP,
1688                            DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP,
1689                            displayMetrics);
1690                    final int autoSizeMaxTextSizeInPx = (int) TypedValue.applyDimension(
1691                            TypedValue.COMPLEX_UNIT_SP,
1692                            DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP,
1693                            displayMetrics);
1694
1695                    validateAndSetAutoSizeTextTypeUniformConfiguration(
1696                            autoSizeMinTextSizeInPx,
1697                            autoSizeMaxTextSizeInPx,
1698                            DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX);
1699                    setupAutoSizeText();
1700                    break;
1701                default:
1702                    throw new IllegalArgumentException(
1703                            "Unknown auto-size text type: " + autoSizeTextType);
1704            }
1705        }
1706    }
1707
1708    /**
1709     * Specify whether this widget should automatically scale the text to try to perfectly fit
1710     * within the layout bounds. If all the configuration params are valid the type of auto-size is
1711     * set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}.
1712     *
1713     * @param autoSizeMinTextSize the minimum text size available for auto-size
1714     * @param autoSizeMaxTextSize the maximum text size available for auto-size
1715     * @param autoSizeStepGranularity the auto-size step granularity. It is used in conjunction with
1716     *                                the minimum and maximum text size in order to build the set of
1717     *                                text sizes the system uses to choose from when auto-sizing
1718     * @param unit the desired dimension unit for all sizes above. See {@link TypedValue} for the
1719     *             possible dimension units
1720     *
1721     * @throws IllegalArgumentException if any of the configuration params are invalid.
1722     *
1723     * @attr ref android.R.styleable#TextView_autoSizeTextType
1724     * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
1725     * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
1726     * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
1727     *
1728     * @see #setAutoSizeTextTypeWithDefaults(int)
1729     * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1730     * @see #getAutoSizeMinTextSize()
1731     * @see #getAutoSizeMaxTextSize()
1732     * @see #getAutoSizeStepGranularity()
1733     * @see #getAutoSizeTextAvailableSizes()
1734     */
1735    public void setAutoSizeTextTypeUniformWithConfiguration(
1736            int autoSizeMinTextSize,
1737            int autoSizeMaxTextSize,
1738            int autoSizeStepGranularity,
1739            int unit) throws IllegalArgumentException {
1740        if (supportsAutoSizeText()) {
1741            final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1742            final int autoSizeMinTextSizeInPx = (int) TypedValue.applyDimension(
1743                    unit, autoSizeMinTextSize, displayMetrics);
1744            final int autoSizeMaxTextSizeInPx = (int) TypedValue.applyDimension(
1745                    unit, autoSizeMaxTextSize, displayMetrics);
1746            final int autoSizeStepGranularityInPx = (int) TypedValue.applyDimension(
1747                    unit, autoSizeStepGranularity, displayMetrics);
1748
1749            validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx,
1750                    autoSizeMaxTextSizeInPx,
1751                    autoSizeStepGranularityInPx);
1752            setupAutoSizeText();
1753        }
1754    }
1755
1756    /**
1757     * Specify whether this widget should automatically scale the text to try to perfectly fit
1758     * within the layout bounds. If at least one value from the <code>presetSizes</code> is valid
1759     * then the type of auto-size is set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}.
1760     *
1761     * @param presetSizes an {@code int} array of sizes in pixels
1762     * @param unit the desired dimension unit for the preset sizes above. See {@link TypedValue} for
1763     *             the possible dimension units
1764     *
1765     * @throws IllegalArgumentException if all of the <code>presetSizes</code> are invalid.
1766     *
1767     * @attr ref android.R.styleable#TextView_autoSizeTextType
1768     * @attr ref android.R.styleable#TextView_autoSizePresetSizes
1769     *
1770     * @see #setAutoSizeTextTypeWithDefaults(int)
1771     * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1772     * @see #getAutoSizeMinTextSize()
1773     * @see #getAutoSizeMaxTextSize()
1774     * @see #getAutoSizeTextAvailableSizes()
1775     */
1776    public void setAutoSizeTextTypeUniformWithPresetSizes(@NonNull int[] presetSizes, int unit)
1777            throws IllegalArgumentException {
1778        if (supportsAutoSizeText()) {
1779            final int presetSizesLength = presetSizes.length;
1780            if (presetSizesLength > 0) {
1781                int[] presetSizesInPx = new int[presetSizesLength];
1782
1783                if (unit == TypedValue.COMPLEX_UNIT_PX) {
1784                    presetSizesInPx = Arrays.copyOf(presetSizes, presetSizesLength);
1785                } else {
1786                    final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1787                    // Convert all to sizes to pixels.
1788                    for (int i = 0; i < presetSizesLength; i++) {
1789                        presetSizesInPx[i] = (int) TypedValue.applyDimension(unit, presetSizes[i],
1790                            displayMetrics);
1791                    }
1792                }
1793
1794                mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(presetSizesInPx);
1795                final int sizesLength = mAutoSizeTextSizesInPx.length;
1796                mHasPresetAutoSizeValues = sizesLength > 0;
1797                if (mHasPresetAutoSizeValues) {
1798                    mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM;
1799                    mAutoSizeMinTextSizeInPx = mAutoSizeTextSizesInPx[0];
1800                    mAutoSizeMaxTextSizeInPx = mAutoSizeTextSizesInPx[sizesLength - 1];
1801                    mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1802                } else {
1803                    throw new IllegalArgumentException("None of the preset sizes is valid: "
1804                        + Arrays.toString(presetSizes));
1805                }
1806            } else {
1807                mHasPresetAutoSizeValues = false;
1808            }
1809            setupAutoSizeText();
1810        }
1811    }
1812
1813    /**
1814     * Returns the type of auto-size set for this widget.
1815     *
1816     * @return an {@code int} corresponding to one of the auto-size types:
1817     *         {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or
1818     *         {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM}
1819     *
1820     * @attr ref android.R.styleable#TextView_autoSizeTextType
1821     *
1822     * @see #setAutoSizeTextTypeWithDefaults(int)
1823     * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1824     * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1825     */
1826    @AutoSizeTextType
1827    public int getAutoSizeTextType() {
1828        return mAutoSizeTextType;
1829    }
1830
1831    /**
1832     * @return the current auto-size step granularity in pixels.
1833     *
1834     * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
1835     *
1836     * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1837     */
1838    public int getAutoSizeStepGranularity() {
1839        return mAutoSizeStepGranularityInPx;
1840    }
1841
1842    /**
1843     * @return the current auto-size minimum text size in pixels (the default is 12sp). Note that
1844     *         if auto-size has not been configured this function returns {@code -1}.
1845     *
1846     * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
1847     *
1848     * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1849     * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1850     */
1851    public int getAutoSizeMinTextSize() {
1852        return mAutoSizeMinTextSizeInPx;
1853    }
1854
1855    /**
1856     * @return the current auto-size maximum text size in pixels (the default is 112sp). Note that
1857     *         if auto-size has not been configured this function returns {@code -1}.
1858     *
1859     * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
1860     *
1861     * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1862     * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1863     */
1864    public int getAutoSizeMaxTextSize() {
1865        return mAutoSizeMaxTextSizeInPx;
1866    }
1867
1868    /**
1869     * @return the current auto-size {@code int} sizes array (in pixels).
1870     *
1871     * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1872     * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1873     */
1874    public int[] getAutoSizeTextAvailableSizes() {
1875        return mAutoSizeTextSizesInPx;
1876    }
1877
1878    private void setupAutoSizeUniformPresetSizes(TypedArray textSizes) {
1879        final int textSizesLength = textSizes.length();
1880        final int[] parsedSizes = new int[textSizesLength];
1881
1882        if (textSizesLength > 0) {
1883            for (int i = 0; i < textSizesLength; i++) {
1884                parsedSizes[i] = textSizes.getDimensionPixelSize(i, -1);
1885            }
1886            mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(parsedSizes);
1887            mHasPresetAutoSizeValues = mAutoSizeTextSizesInPx.length > 0;
1888        }
1889    }
1890
1891    /**
1892     * If all params are valid then save the auto-size configuration.
1893     *
1894     * @throws IllegalArgumentException if any of the params are invalid
1895     */
1896    private void validateAndSetAutoSizeTextTypeUniformConfiguration(
1897            int autoSizeMinTextSizeInPx,
1898            int autoSizeMaxTextSizeInPx,
1899            int autoSizeStepGranularityInPx) throws IllegalArgumentException {
1900        // First validate.
1901        if (autoSizeMinTextSizeInPx <= 0) {
1902            throw new IllegalArgumentException("Minimum auto-size text size ("
1903                + autoSizeMinTextSizeInPx  + "px) is less or equal to 0px)");
1904        }
1905
1906        if (autoSizeMaxTextSizeInPx <= autoSizeMinTextSizeInPx) {
1907            throw new IllegalArgumentException("Maximum auto-size text size ("
1908                + autoSizeMaxTextSizeInPx + "px) is less or equal to minimum auto-size "
1909                + "text size (" + autoSizeMinTextSizeInPx + "px)");
1910        }
1911
1912        if (autoSizeStepGranularityInPx <= 0) {
1913            throw new IllegalArgumentException("Minimum auto-size text size ("
1914                + autoSizeStepGranularityInPx + "px) is less or equal to 0px)");
1915        }
1916
1917        // All good, persist the configuration.
1918        mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM;
1919        mAutoSizeMinTextSizeInPx = autoSizeMinTextSizeInPx;
1920        mAutoSizeMaxTextSizeInPx = autoSizeMaxTextSizeInPx;
1921        mAutoSizeStepGranularityInPx = autoSizeStepGranularityInPx;
1922        mHasPresetAutoSizeValues = false;
1923    }
1924
1925    private void clearAutoSizeConfiguration() {
1926        mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
1927        mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1928        mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1929        mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1930        mAutoSizeTextSizesInPx = EmptyArray.INT;
1931        mNeedsAutoSizeText = false;
1932    }
1933
1934    // Returns distinct sorted positive values.
1935    private int[] cleanupAutoSizePresetSizes(int[] presetValues) {
1936        final int presetValuesLength = presetValues.length;
1937        if (presetValuesLength == 0) {
1938            return presetValues;
1939        }
1940        Arrays.sort(presetValues);
1941
1942        final IntArray uniqueValidSizes = new IntArray();
1943        for (int i = 0; i < presetValuesLength; i++) {
1944            final int currentPresetValue = presetValues[i];
1945
1946            if (currentPresetValue > 0
1947                    && uniqueValidSizes.binarySearch(currentPresetValue) < 0) {
1948                uniqueValidSizes.add(currentPresetValue);
1949            }
1950        }
1951
1952        return presetValuesLength == uniqueValidSizes.size()
1953            ? presetValues
1954            : uniqueValidSizes.toArray();
1955    }
1956
1957    private void setupAutoSizeText() {
1958        if (supportsAutoSizeText() && mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) {
1959            // Calculate the sizes set based on minimum size, maximum size and step size if we do
1960            // not have a predefined set of sizes or if the current sizes array is empty.
1961            if (!mHasPresetAutoSizeValues || mAutoSizeTextSizesInPx.length == 0) {
1962                // Calculate sizes to choose from based on the current auto-size configuration.
1963                int autoSizeValuesLength = (int) Math.ceil(
1964                        (mAutoSizeMaxTextSizeInPx - mAutoSizeMinTextSizeInPx)
1965                                / (float) mAutoSizeStepGranularityInPx);
1966                // Also reserve a slot for the max size if it fits.
1967                if ((mAutoSizeMaxTextSizeInPx - mAutoSizeMinTextSizeInPx)
1968                        % mAutoSizeStepGranularityInPx == 0) {
1969                    autoSizeValuesLength++;
1970                }
1971                mAutoSizeTextSizesInPx = new int[autoSizeValuesLength];
1972                int sizeToAdd = mAutoSizeMinTextSizeInPx;
1973                for (int i = 0; i < autoSizeValuesLength; i++) {
1974                    mAutoSizeTextSizesInPx[i] = sizeToAdd;
1975                    sizeToAdd += mAutoSizeStepGranularityInPx;
1976                }
1977            }
1978
1979            mNeedsAutoSizeText = true;
1980            autoSizeText();
1981        }
1982    }
1983
1984    private int[] parseDimensionArray(TypedArray dimens) {
1985        if (dimens == null) {
1986            return null;
1987        }
1988        int[] result = new int[dimens.length()];
1989        for (int i = 0; i < result.length; i++) {
1990            result[i] = dimens.getDimensionPixelSize(i, 0);
1991        }
1992        return result;
1993    }
1994
1995    /**
1996     * @hide
1997     */
1998    @Override
1999    public void onActivityResult(int requestCode, int resultCode, Intent data) {
2000        if (requestCode == PROCESS_TEXT_REQUEST_CODE) {
2001            if (resultCode == Activity.RESULT_OK && data != null) {
2002                CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT);
2003                if (result != null) {
2004                    if (isTextEditable()) {
2005                        replaceSelectionWithText(result);
2006                        if (mEditor != null) {
2007                            mEditor.refreshTextActionMode();
2008                        }
2009                    } else {
2010                        if (result.length() > 0) {
2011                            Toast.makeText(getContext(), String.valueOf(result), Toast.LENGTH_LONG)
2012                                .show();
2013                        }
2014                    }
2015                }
2016            } else if (mText instanceof Spannable) {
2017                // Reset the selection.
2018                Selection.setSelection((Spannable) mText, getSelectionEnd());
2019            }
2020        }
2021    }
2022
2023    private void setTypefaceFromAttrs(Typeface fontTypeface, String familyName, int typefaceIndex,
2024            int styleIndex) {
2025        Typeface tf = fontTypeface;
2026        if (tf == null && familyName != null) {
2027            tf = Typeface.create(familyName, styleIndex);
2028        }
2029        if (tf != null) {
2030            setTypeface(tf);
2031            return;
2032        }
2033        switch (typefaceIndex) {
2034            case SANS:
2035                tf = Typeface.SANS_SERIF;
2036                break;
2037
2038            case SERIF:
2039                tf = Typeface.SERIF;
2040                break;
2041
2042            case MONOSPACE:
2043                tf = Typeface.MONOSPACE;
2044                break;
2045        }
2046
2047        setTypeface(tf, styleIndex);
2048    }
2049
2050    private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) {
2051        boolean hasRelativeDrawables = (start != null) || (end != null);
2052        if (hasRelativeDrawables) {
2053            Drawables dr = mDrawables;
2054            if (dr == null) {
2055                mDrawables = dr = new Drawables(getContext());
2056            }
2057            mDrawables.mOverride = true;
2058            final Rect compoundRect = dr.mCompoundRect;
2059            int[] state = getDrawableState();
2060            if (start != null) {
2061                start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
2062                start.setState(state);
2063                start.copyBounds(compoundRect);
2064                start.setCallback(this);
2065
2066                dr.mDrawableStart = start;
2067                dr.mDrawableSizeStart = compoundRect.width();
2068                dr.mDrawableHeightStart = compoundRect.height();
2069            } else {
2070                dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2071            }
2072            if (end != null) {
2073                end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
2074                end.setState(state);
2075                end.copyBounds(compoundRect);
2076                end.setCallback(this);
2077
2078                dr.mDrawableEnd = end;
2079                dr.mDrawableSizeEnd = compoundRect.width();
2080                dr.mDrawableHeightEnd = compoundRect.height();
2081            } else {
2082                dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2083            }
2084            resetResolvedDrawables();
2085            resolveDrawables();
2086            applyCompoundDrawableTint();
2087        }
2088    }
2089
2090    @android.view.RemotableViewMethod
2091    @Override
2092    public void setEnabled(boolean enabled) {
2093        if (enabled == isEnabled()) {
2094            return;
2095        }
2096
2097        if (!enabled) {
2098            // Hide the soft input if the currently active TextView is disabled
2099            InputMethodManager imm = InputMethodManager.peekInstance();
2100            if (imm != null && imm.isActive(this)) {
2101                imm.hideSoftInputFromWindow(getWindowToken(), 0);
2102            }
2103        }
2104
2105        super.setEnabled(enabled);
2106
2107        if (enabled) {
2108            // Make sure IME is updated with current editor info.
2109            InputMethodManager imm = InputMethodManager.peekInstance();
2110            if (imm != null) imm.restartInput(this);
2111        }
2112
2113        // Will change text color
2114        if (mEditor != null) {
2115            mEditor.invalidateTextDisplayList();
2116            mEditor.prepareCursorControllers();
2117
2118            // start or stop the cursor blinking as appropriate
2119            mEditor.makeBlink();
2120        }
2121    }
2122
2123    /**
2124     * Sets the typeface and style in which the text should be displayed,
2125     * and turns on the fake bold and italic bits in the Paint if the
2126     * Typeface that you provided does not have all the bits in the
2127     * style that you specified.
2128     *
2129     * @attr ref android.R.styleable#TextView_typeface
2130     * @attr ref android.R.styleable#TextView_textStyle
2131     */
2132    public void setTypeface(Typeface tf, int style) {
2133        if (style > 0) {
2134            if (tf == null) {
2135                tf = Typeface.defaultFromStyle(style);
2136            } else {
2137                tf = Typeface.create(tf, style);
2138            }
2139
2140            setTypeface(tf);
2141            // now compute what (if any) algorithmic styling is needed
2142            int typefaceStyle = tf != null ? tf.getStyle() : 0;
2143            int need = style & ~typefaceStyle;
2144            mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
2145            mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
2146        } else {
2147            mTextPaint.setFakeBoldText(false);
2148            mTextPaint.setTextSkewX(0);
2149            setTypeface(tf);
2150        }
2151    }
2152
2153    /**
2154     * Subclasses override this to specify that they have a KeyListener
2155     * by default even if not specifically called for in the XML options.
2156     */
2157    protected boolean getDefaultEditable() {
2158        return false;
2159    }
2160
2161    /**
2162     * Subclasses override this to specify a default movement method.
2163     */
2164    protected MovementMethod getDefaultMovementMethod() {
2165        return null;
2166    }
2167
2168    /**
2169     * Return the text that TextView is displaying. If {@link #setText(CharSequence)} was called
2170     * with an argument of {@link android.widget.TextView.BufferType#SPANNABLE BufferType.SPANNABLE}
2171     * or {@link android.widget.TextView.BufferType#EDITABLE BufferType.EDITABLE}, you can cast
2172     * the return value from this method to Spannable or Editable, respectively.
2173     * <p/>
2174     * The content of the return value should not be modified. If you want a modifiable one, you
2175     * should make your own copy first.
2176     *
2177     * @attr ref android.R.styleable#TextView_text
2178     */
2179    @ViewDebug.CapturedViewProperty
2180    public CharSequence getText() {
2181        return mText;
2182    }
2183
2184    /**
2185     * Returns the length, in characters, of the text managed by this TextView
2186     */
2187    public int length() {
2188        return mText.length();
2189    }
2190
2191    /**
2192     * Return the text that TextView is displaying as an Editable object. If the text is not
2193     * editable, null is returned.
2194     *
2195     * @see #getText
2196     */
2197    public Editable getEditableText() {
2198        return (mText instanceof Editable) ? (Editable) mText : null;
2199    }
2200
2201    /**
2202     * @return the height of one standard line in pixels.  Note that markup
2203     * within the text can cause individual lines to be taller or shorter
2204     * than this height, and the layout may contain additional first-
2205     * or last-line padding.
2206     */
2207    public int getLineHeight() {
2208        return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd);
2209    }
2210
2211    /**
2212     * @return the Layout that is currently being used to display the text.
2213     * This can be null if the text or width has recently changes.
2214     */
2215    public final Layout getLayout() {
2216        return mLayout;
2217    }
2218
2219    /**
2220     * @return the Layout that is currently being used to display the hint text.
2221     * This can be null.
2222     */
2223    final Layout getHintLayout() {
2224        return mHintLayout;
2225    }
2226
2227    /**
2228     * Retrieve the {@link android.content.UndoManager} that is currently associated
2229     * with this TextView.  By default there is no associated UndoManager, so null
2230     * is returned.  One can be associated with the TextView through
2231     * {@link #setUndoManager(android.content.UndoManager, String)}
2232     *
2233     * @hide
2234     */
2235    public final UndoManager getUndoManager() {
2236        // TODO: Consider supporting a global undo manager.
2237        throw new UnsupportedOperationException("not implemented");
2238    }
2239
2240
2241    /**
2242     * @hide
2243     */
2244    @VisibleForTesting
2245    public final Editor getEditorForTesting() {
2246        return mEditor;
2247    }
2248
2249    /**
2250     * Associate an {@link android.content.UndoManager} with this TextView.  Once
2251     * done, all edit operations on the TextView will result in appropriate
2252     * {@link android.content.UndoOperation} objects pushed on the given UndoManager's
2253     * stack.
2254     *
2255     * @param undoManager The {@link android.content.UndoManager} to associate with
2256     * this TextView, or null to clear any existing association.
2257     * @param tag String tag identifying this particular TextView owner in the
2258     * UndoManager.  This is used to keep the correct association with the
2259     * {@link android.content.UndoOwner} of any operations inside of the UndoManager.
2260     *
2261     * @hide
2262     */
2263    public final void setUndoManager(UndoManager undoManager, String tag) {
2264        // TODO: Consider supporting a global undo manager. An implementation will need to:
2265        // * createEditorIfNeeded()
2266        // * Promote to BufferType.EDITABLE if needed.
2267        // * Update the UndoManager and UndoOwner.
2268        // Likewise it will need to be able to restore the default UndoManager.
2269        throw new UnsupportedOperationException("not implemented");
2270    }
2271
2272    /**
2273     * @return the current key listener for this TextView.
2274     * This will frequently be null for non-EditText TextViews.
2275     *
2276     * @attr ref android.R.styleable#TextView_numeric
2277     * @attr ref android.R.styleable#TextView_digits
2278     * @attr ref android.R.styleable#TextView_phoneNumber
2279     * @attr ref android.R.styleable#TextView_inputMethod
2280     * @attr ref android.R.styleable#TextView_capitalize
2281     * @attr ref android.R.styleable#TextView_autoText
2282     */
2283    public final KeyListener getKeyListener() {
2284        return mEditor == null ? null : mEditor.mKeyListener;
2285    }
2286
2287    /**
2288     * Sets the key listener to be used with this TextView.  This can be null
2289     * to disallow user input.  Note that this method has significant and
2290     * subtle interactions with soft keyboards and other input method:
2291     * see {@link KeyListener#getInputType() KeyListener.getContentType()}
2292     * for important details.  Calling this method will replace the current
2293     * content type of the text view with the content type returned by the
2294     * key listener.
2295     * <p>
2296     * Be warned that if you want a TextView with a key listener or movement
2297     * method not to be focusable, or if you want a TextView without a
2298     * key listener or movement method to be focusable, you must call
2299     * {@link #setFocusable} again after calling this to get the focusability
2300     * back the way you want it.
2301     *
2302     * @attr ref android.R.styleable#TextView_numeric
2303     * @attr ref android.R.styleable#TextView_digits
2304     * @attr ref android.R.styleable#TextView_phoneNumber
2305     * @attr ref android.R.styleable#TextView_inputMethod
2306     * @attr ref android.R.styleable#TextView_capitalize
2307     * @attr ref android.R.styleable#TextView_autoText
2308     */
2309    public void setKeyListener(KeyListener input) {
2310        setKeyListenerOnly(input);
2311        fixFocusableAndClickableSettings();
2312
2313        if (input != null) {
2314            createEditorIfNeeded();
2315            try {
2316                mEditor.mInputType = mEditor.mKeyListener.getInputType();
2317            } catch (IncompatibleClassChangeError e) {
2318                mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
2319            }
2320            // Change inputType, without affecting transformation.
2321            // No need to applySingleLine since mSingleLine is unchanged.
2322            setInputTypeSingleLine(mSingleLine);
2323        } else {
2324            if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL;
2325        }
2326
2327        InputMethodManager imm = InputMethodManager.peekInstance();
2328        if (imm != null) imm.restartInput(this);
2329    }
2330
2331    private void setKeyListenerOnly(KeyListener input) {
2332        if (mEditor == null && input == null) return; // null is the default value
2333
2334        createEditorIfNeeded();
2335        if (mEditor.mKeyListener != input) {
2336            mEditor.mKeyListener = input;
2337            if (input != null && !(mText instanceof Editable)) {
2338                setText(mText);
2339            }
2340
2341            setFilters((Editable) mText, mFilters);
2342        }
2343    }
2344
2345    /**
2346     * @return the movement method being used for this TextView.
2347     * This will frequently be null for non-EditText TextViews.
2348     */
2349    public final MovementMethod getMovementMethod() {
2350        return mMovement;
2351    }
2352
2353    /**
2354     * Sets the movement method (arrow key handler) to be used for
2355     * this TextView.  This can be null to disallow using the arrow keys
2356     * to move the cursor or scroll the view.
2357     * <p>
2358     * Be warned that if you want a TextView with a key listener or movement
2359     * method not to be focusable, or if you want a TextView without a
2360     * key listener or movement method to be focusable, you must call
2361     * {@link #setFocusable} again after calling this to get the focusability
2362     * back the way you want it.
2363     */
2364    public final void setMovementMethod(MovementMethod movement) {
2365        if (mMovement != movement) {
2366            mMovement = movement;
2367
2368            if (movement != null && !(mText instanceof Spannable)) {
2369                setText(mText);
2370            }
2371
2372            fixFocusableAndClickableSettings();
2373
2374            // SelectionModifierCursorController depends on textCanBeSelected, which depends on
2375            // mMovement
2376            if (mEditor != null) mEditor.prepareCursorControllers();
2377        }
2378    }
2379
2380    private void fixFocusableAndClickableSettings() {
2381        if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) {
2382            setFocusable(FOCUSABLE);
2383            setClickable(true);
2384            setLongClickable(true);
2385        } else {
2386            setFocusable(FOCUSABLE_AUTO);
2387            setClickable(false);
2388            setLongClickable(false);
2389        }
2390    }
2391
2392    /**
2393     * @return the current transformation method for this TextView.
2394     * This will frequently be null except for single-line and password
2395     * fields.
2396     *
2397     * @attr ref android.R.styleable#TextView_password
2398     * @attr ref android.R.styleable#TextView_singleLine
2399     */
2400    public final TransformationMethod getTransformationMethod() {
2401        return mTransformation;
2402    }
2403
2404    /**
2405     * Sets the transformation that is applied to the text that this
2406     * TextView is displaying.
2407     *
2408     * @attr ref android.R.styleable#TextView_password
2409     * @attr ref android.R.styleable#TextView_singleLine
2410     */
2411    public final void setTransformationMethod(TransformationMethod method) {
2412        if (method == mTransformation) {
2413            // Avoid the setText() below if the transformation is
2414            // the same.
2415            return;
2416        }
2417        if (mTransformation != null) {
2418            if (mText instanceof Spannable) {
2419                ((Spannable) mText).removeSpan(mTransformation);
2420            }
2421        }
2422
2423        mTransformation = method;
2424
2425        if (method instanceof TransformationMethod2) {
2426            TransformationMethod2 method2 = (TransformationMethod2) method;
2427            mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable);
2428            method2.setLengthChangesAllowed(mAllowTransformationLengthChange);
2429        } else {
2430            mAllowTransformationLengthChange = false;
2431        }
2432
2433        setText(mText);
2434
2435        if (hasPasswordTransformationMethod()) {
2436            notifyViewAccessibilityStateChangedIfNeeded(
2437                    AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
2438        }
2439
2440        // PasswordTransformationMethod always have LTR text direction heuristics returned by
2441        // getTextDirectionHeuristic, needs reset
2442        mTextDir = getTextDirectionHeuristic();
2443    }
2444
2445    /**
2446     * Returns the top padding of the view, plus space for the top
2447     * Drawable if any.
2448     */
2449    public int getCompoundPaddingTop() {
2450        final Drawables dr = mDrawables;
2451        if (dr == null || dr.mShowing[Drawables.TOP] == null) {
2452            return mPaddingTop;
2453        } else {
2454            return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
2455        }
2456    }
2457
2458    /**
2459     * Returns the bottom padding of the view, plus space for the bottom
2460     * Drawable if any.
2461     */
2462    public int getCompoundPaddingBottom() {
2463        final Drawables dr = mDrawables;
2464        if (dr == null || dr.mShowing[Drawables.BOTTOM] == null) {
2465            return mPaddingBottom;
2466        } else {
2467            return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
2468        }
2469    }
2470
2471    /**
2472     * Returns the left padding of the view, plus space for the left
2473     * Drawable if any.
2474     */
2475    public int getCompoundPaddingLeft() {
2476        final Drawables dr = mDrawables;
2477        if (dr == null || dr.mShowing[Drawables.LEFT] == null) {
2478            return mPaddingLeft;
2479        } else {
2480            return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
2481        }
2482    }
2483
2484    /**
2485     * Returns the right padding of the view, plus space for the right
2486     * Drawable if any.
2487     */
2488    public int getCompoundPaddingRight() {
2489        final Drawables dr = mDrawables;
2490        if (dr == null || dr.mShowing[Drawables.RIGHT] == null) {
2491            return mPaddingRight;
2492        } else {
2493            return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
2494        }
2495    }
2496
2497    /**
2498     * Returns the start padding of the view, plus space for the start
2499     * Drawable if any.
2500     */
2501    public int getCompoundPaddingStart() {
2502        resolveDrawables();
2503        switch(getLayoutDirection()) {
2504            default:
2505            case LAYOUT_DIRECTION_LTR:
2506                return getCompoundPaddingLeft();
2507            case LAYOUT_DIRECTION_RTL:
2508                return getCompoundPaddingRight();
2509        }
2510    }
2511
2512    /**
2513     * Returns the end padding of the view, plus space for the end
2514     * Drawable if any.
2515     */
2516    public int getCompoundPaddingEnd() {
2517        resolveDrawables();
2518        switch(getLayoutDirection()) {
2519            default:
2520            case LAYOUT_DIRECTION_LTR:
2521                return getCompoundPaddingRight();
2522            case LAYOUT_DIRECTION_RTL:
2523                return getCompoundPaddingLeft();
2524        }
2525    }
2526
2527    /**
2528     * Returns the extended top padding of the view, including both the
2529     * top Drawable if any and any extra space to keep more than maxLines
2530     * of text from showing.  It is only valid to call this after measuring.
2531     */
2532    public int getExtendedPaddingTop() {
2533        if (mMaxMode != LINES) {
2534            return getCompoundPaddingTop();
2535        }
2536
2537        if (mLayout == null) {
2538            assumeLayout();
2539        }
2540
2541        if (mLayout.getLineCount() <= mMaximum) {
2542            return getCompoundPaddingTop();
2543        }
2544
2545        int top = getCompoundPaddingTop();
2546        int bottom = getCompoundPaddingBottom();
2547        int viewht = getHeight() - top - bottom;
2548        int layoutht = mLayout.getLineTop(mMaximum);
2549
2550        if (layoutht >= viewht) {
2551            return top;
2552        }
2553
2554        final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2555        if (gravity == Gravity.TOP) {
2556            return top;
2557        } else if (gravity == Gravity.BOTTOM) {
2558            return top + viewht - layoutht;
2559        } else { // (gravity == Gravity.CENTER_VERTICAL)
2560            return top + (viewht - layoutht) / 2;
2561        }
2562    }
2563
2564    /**
2565     * Returns the extended bottom padding of the view, including both the
2566     * bottom Drawable if any and any extra space to keep more than maxLines
2567     * of text from showing.  It is only valid to call this after measuring.
2568     */
2569    public int getExtendedPaddingBottom() {
2570        if (mMaxMode != LINES) {
2571            return getCompoundPaddingBottom();
2572        }
2573
2574        if (mLayout == null) {
2575            assumeLayout();
2576        }
2577
2578        if (mLayout.getLineCount() <= mMaximum) {
2579            return getCompoundPaddingBottom();
2580        }
2581
2582        int top = getCompoundPaddingTop();
2583        int bottom = getCompoundPaddingBottom();
2584        int viewht = getHeight() - top - bottom;
2585        int layoutht = mLayout.getLineTop(mMaximum);
2586
2587        if (layoutht >= viewht) {
2588            return bottom;
2589        }
2590
2591        final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2592        if (gravity == Gravity.TOP) {
2593            return bottom + viewht - layoutht;
2594        } else if (gravity == Gravity.BOTTOM) {
2595            return bottom;
2596        } else { // (gravity == Gravity.CENTER_VERTICAL)
2597            return bottom + (viewht - layoutht) / 2;
2598        }
2599    }
2600
2601    /**
2602     * Returns the total left padding of the view, including the left
2603     * Drawable if any.
2604     */
2605    public int getTotalPaddingLeft() {
2606        return getCompoundPaddingLeft();
2607    }
2608
2609    /**
2610     * Returns the total right padding of the view, including the right
2611     * Drawable if any.
2612     */
2613    public int getTotalPaddingRight() {
2614        return getCompoundPaddingRight();
2615    }
2616
2617    /**
2618     * Returns the total start padding of the view, including the start
2619     * Drawable if any.
2620     */
2621    public int getTotalPaddingStart() {
2622        return getCompoundPaddingStart();
2623    }
2624
2625    /**
2626     * Returns the total end padding of the view, including the end
2627     * Drawable if any.
2628     */
2629    public int getTotalPaddingEnd() {
2630        return getCompoundPaddingEnd();
2631    }
2632
2633    /**
2634     * Returns the total top padding of the view, including the top
2635     * Drawable if any, the extra space to keep more than maxLines
2636     * from showing, and the vertical offset for gravity, if any.
2637     */
2638    public int getTotalPaddingTop() {
2639        return getExtendedPaddingTop() + getVerticalOffset(true);
2640    }
2641
2642    /**
2643     * Returns the total bottom padding of the view, including the bottom
2644     * Drawable if any, the extra space to keep more than maxLines
2645     * from showing, and the vertical offset for gravity, if any.
2646     */
2647    public int getTotalPaddingBottom() {
2648        return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
2649    }
2650
2651    /**
2652     * Sets the Drawables (if any) to appear to the left of, above, to the
2653     * right of, and below the text. Use {@code null} if you do not want a
2654     * Drawable there. The Drawables must already have had
2655     * {@link Drawable#setBounds} called.
2656     * <p>
2657     * Calling this method will overwrite any Drawables previously set using
2658     * {@link #setCompoundDrawablesRelative} or related methods.
2659     *
2660     * @attr ref android.R.styleable#TextView_drawableLeft
2661     * @attr ref android.R.styleable#TextView_drawableTop
2662     * @attr ref android.R.styleable#TextView_drawableRight
2663     * @attr ref android.R.styleable#TextView_drawableBottom
2664     */
2665    public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top,
2666            @Nullable Drawable right, @Nullable Drawable bottom) {
2667        Drawables dr = mDrawables;
2668
2669        // We're switching to absolute, discard relative.
2670        if (dr != null) {
2671            if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
2672            dr.mDrawableStart = null;
2673            if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null);
2674            dr.mDrawableEnd = null;
2675            dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2676            dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2677        }
2678
2679        final boolean drawables = left != null || top != null || right != null || bottom != null;
2680        if (!drawables) {
2681            // Clearing drawables...  can we free the data structure?
2682            if (dr != null) {
2683                if (!dr.hasMetadata()) {
2684                    mDrawables = null;
2685                } else {
2686                    // We need to retain the last set padding, so just clear
2687                    // out all of the fields in the existing structure.
2688                    for (int i = dr.mShowing.length - 1; i >= 0; i--) {
2689                        if (dr.mShowing[i] != null) {
2690                            dr.mShowing[i].setCallback(null);
2691                        }
2692                        dr.mShowing[i] = null;
2693                    }
2694                    dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2695                    dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2696                    dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2697                    dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2698                }
2699            }
2700        } else {
2701            if (dr == null) {
2702                mDrawables = dr = new Drawables(getContext());
2703            }
2704
2705            mDrawables.mOverride = false;
2706
2707            if (dr.mShowing[Drawables.LEFT] != left && dr.mShowing[Drawables.LEFT] != null) {
2708                dr.mShowing[Drawables.LEFT].setCallback(null);
2709            }
2710            dr.mShowing[Drawables.LEFT] = left;
2711
2712            if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
2713                dr.mShowing[Drawables.TOP].setCallback(null);
2714            }
2715            dr.mShowing[Drawables.TOP] = top;
2716
2717            if (dr.mShowing[Drawables.RIGHT] != right && dr.mShowing[Drawables.RIGHT] != null) {
2718                dr.mShowing[Drawables.RIGHT].setCallback(null);
2719            }
2720            dr.mShowing[Drawables.RIGHT] = right;
2721
2722            if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
2723                dr.mShowing[Drawables.BOTTOM].setCallback(null);
2724            }
2725            dr.mShowing[Drawables.BOTTOM] = bottom;
2726
2727            final Rect compoundRect = dr.mCompoundRect;
2728            int[] state;
2729
2730            state = getDrawableState();
2731
2732            if (left != null) {
2733                left.setState(state);
2734                left.copyBounds(compoundRect);
2735                left.setCallback(this);
2736                dr.mDrawableSizeLeft = compoundRect.width();
2737                dr.mDrawableHeightLeft = compoundRect.height();
2738            } else {
2739                dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2740            }
2741
2742            if (right != null) {
2743                right.setState(state);
2744                right.copyBounds(compoundRect);
2745                right.setCallback(this);
2746                dr.mDrawableSizeRight = compoundRect.width();
2747                dr.mDrawableHeightRight = compoundRect.height();
2748            } else {
2749                dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2750            }
2751
2752            if (top != null) {
2753                top.setState(state);
2754                top.copyBounds(compoundRect);
2755                top.setCallback(this);
2756                dr.mDrawableSizeTop = compoundRect.height();
2757                dr.mDrawableWidthTop = compoundRect.width();
2758            } else {
2759                dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2760            }
2761
2762            if (bottom != null) {
2763                bottom.setState(state);
2764                bottom.copyBounds(compoundRect);
2765                bottom.setCallback(this);
2766                dr.mDrawableSizeBottom = compoundRect.height();
2767                dr.mDrawableWidthBottom = compoundRect.width();
2768            } else {
2769                dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2770            }
2771        }
2772
2773        // Save initial left/right drawables
2774        if (dr != null) {
2775            dr.mDrawableLeftInitial = left;
2776            dr.mDrawableRightInitial = right;
2777        }
2778
2779        resetResolvedDrawables();
2780        resolveDrawables();
2781        applyCompoundDrawableTint();
2782        invalidate();
2783        requestLayout();
2784    }
2785
2786    /**
2787     * Sets the Drawables (if any) to appear to the left of, above, to the
2788     * right of, and below the text. Use 0 if you do not want a Drawable there.
2789     * The Drawables' bounds will be set to their intrinsic bounds.
2790     * <p>
2791     * Calling this method will overwrite any Drawables previously set using
2792     * {@link #setCompoundDrawablesRelative} or related methods.
2793     *
2794     * @param left Resource identifier of the left Drawable.
2795     * @param top Resource identifier of the top Drawable.
2796     * @param right Resource identifier of the right Drawable.
2797     * @param bottom Resource identifier of the bottom Drawable.
2798     *
2799     * @attr ref android.R.styleable#TextView_drawableLeft
2800     * @attr ref android.R.styleable#TextView_drawableTop
2801     * @attr ref android.R.styleable#TextView_drawableRight
2802     * @attr ref android.R.styleable#TextView_drawableBottom
2803     */
2804    @android.view.RemotableViewMethod
2805    public void setCompoundDrawablesWithIntrinsicBounds(@DrawableRes int left,
2806            @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) {
2807        final Context context = getContext();
2808        setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null,
2809                top != 0 ? context.getDrawable(top) : null,
2810                right != 0 ? context.getDrawable(right) : null,
2811                bottom != 0 ? context.getDrawable(bottom) : null);
2812    }
2813
2814    /**
2815     * Sets the Drawables (if any) to appear to the left of, above, to the
2816     * right of, and below the text. Use {@code null} if you do not want a
2817     * Drawable there. The Drawables' bounds will be set to their intrinsic
2818     * bounds.
2819     * <p>
2820     * Calling this method will overwrite any Drawables previously set using
2821     * {@link #setCompoundDrawablesRelative} or related methods.
2822     *
2823     * @attr ref android.R.styleable#TextView_drawableLeft
2824     * @attr ref android.R.styleable#TextView_drawableTop
2825     * @attr ref android.R.styleable#TextView_drawableRight
2826     * @attr ref android.R.styleable#TextView_drawableBottom
2827     */
2828    @android.view.RemotableViewMethod
2829    public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left,
2830            @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) {
2831
2832        if (left != null) {
2833            left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
2834        }
2835        if (right != null) {
2836            right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
2837        }
2838        if (top != null) {
2839            top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
2840        }
2841        if (bottom != null) {
2842            bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
2843        }
2844        setCompoundDrawables(left, top, right, bottom);
2845    }
2846
2847    /**
2848     * Sets the Drawables (if any) to appear to the start of, above, to the end
2849     * of, and below the text. Use {@code null} if you do not want a Drawable
2850     * there. The Drawables must already have had {@link Drawable#setBounds}
2851     * called.
2852     * <p>
2853     * Calling this method will overwrite any Drawables previously set using
2854     * {@link #setCompoundDrawables} or related methods.
2855     *
2856     * @attr ref android.R.styleable#TextView_drawableStart
2857     * @attr ref android.R.styleable#TextView_drawableTop
2858     * @attr ref android.R.styleable#TextView_drawableEnd
2859     * @attr ref android.R.styleable#TextView_drawableBottom
2860     */
2861    @android.view.RemotableViewMethod
2862    public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top,
2863            @Nullable Drawable end, @Nullable Drawable bottom) {
2864        Drawables dr = mDrawables;
2865
2866        // We're switching to relative, discard absolute.
2867        if (dr != null) {
2868            if (dr.mShowing[Drawables.LEFT] != null) {
2869                dr.mShowing[Drawables.LEFT].setCallback(null);
2870            }
2871            dr.mShowing[Drawables.LEFT] = dr.mDrawableLeftInitial = null;
2872            if (dr.mShowing[Drawables.RIGHT] != null) {
2873                dr.mShowing[Drawables.RIGHT].setCallback(null);
2874            }
2875            dr.mShowing[Drawables.RIGHT] = dr.mDrawableRightInitial = null;
2876            dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2877            dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2878        }
2879
2880        final boolean drawables = start != null || top != null
2881                || end != null || bottom != null;
2882
2883        if (!drawables) {
2884            // Clearing drawables...  can we free the data structure?
2885            if (dr != null) {
2886                if (!dr.hasMetadata()) {
2887                    mDrawables = null;
2888                } else {
2889                    // We need to retain the last set padding, so just clear
2890                    // out all of the fields in the existing structure.
2891                    if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
2892                    dr.mDrawableStart = null;
2893                    if (dr.mShowing[Drawables.TOP] != null) {
2894                        dr.mShowing[Drawables.TOP].setCallback(null);
2895                    }
2896                    dr.mShowing[Drawables.TOP] = null;
2897                    if (dr.mDrawableEnd != null) {
2898                        dr.mDrawableEnd.setCallback(null);
2899                    }
2900                    dr.mDrawableEnd = null;
2901                    if (dr.mShowing[Drawables.BOTTOM] != null) {
2902                        dr.mShowing[Drawables.BOTTOM].setCallback(null);
2903                    }
2904                    dr.mShowing[Drawables.BOTTOM] = null;
2905                    dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2906                    dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2907                    dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2908                    dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2909                }
2910            }
2911        } else {
2912            if (dr == null) {
2913                mDrawables = dr = new Drawables(getContext());
2914            }
2915
2916            mDrawables.mOverride = true;
2917
2918            if (dr.mDrawableStart != start && dr.mDrawableStart != null) {
2919                dr.mDrawableStart.setCallback(null);
2920            }
2921            dr.mDrawableStart = start;
2922
2923            if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
2924                dr.mShowing[Drawables.TOP].setCallback(null);
2925            }
2926            dr.mShowing[Drawables.TOP] = top;
2927
2928            if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) {
2929                dr.mDrawableEnd.setCallback(null);
2930            }
2931            dr.mDrawableEnd = end;
2932
2933            if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
2934                dr.mShowing[Drawables.BOTTOM].setCallback(null);
2935            }
2936            dr.mShowing[Drawables.BOTTOM] = bottom;
2937
2938            final Rect compoundRect = dr.mCompoundRect;
2939            int[] state;
2940
2941            state = getDrawableState();
2942
2943            if (start != null) {
2944                start.setState(state);
2945                start.copyBounds(compoundRect);
2946                start.setCallback(this);
2947                dr.mDrawableSizeStart = compoundRect.width();
2948                dr.mDrawableHeightStart = compoundRect.height();
2949            } else {
2950                dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2951            }
2952
2953            if (end != null) {
2954                end.setState(state);
2955                end.copyBounds(compoundRect);
2956                end.setCallback(this);
2957                dr.mDrawableSizeEnd = compoundRect.width();
2958                dr.mDrawableHeightEnd = compoundRect.height();
2959            } else {
2960                dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2961            }
2962
2963            if (top != null) {
2964                top.setState(state);
2965                top.copyBounds(compoundRect);
2966                top.setCallback(this);
2967                dr.mDrawableSizeTop = compoundRect.height();
2968                dr.mDrawableWidthTop = compoundRect.width();
2969            } else {
2970                dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2971            }
2972
2973            if (bottom != null) {
2974                bottom.setState(state);
2975                bottom.copyBounds(compoundRect);
2976                bottom.setCallback(this);
2977                dr.mDrawableSizeBottom = compoundRect.height();
2978                dr.mDrawableWidthBottom = compoundRect.width();
2979            } else {
2980                dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2981            }
2982        }
2983
2984        resetResolvedDrawables();
2985        resolveDrawables();
2986        invalidate();
2987        requestLayout();
2988    }
2989
2990    /**
2991     * Sets the Drawables (if any) to appear to the start of, above, to the end
2992     * of, and below the text. Use 0 if you do not want a Drawable there. The
2993     * Drawables' bounds will be set to their intrinsic bounds.
2994     * <p>
2995     * Calling this method will overwrite any Drawables previously set using
2996     * {@link #setCompoundDrawables} or related methods.
2997     *
2998     * @param start Resource identifier of the start Drawable.
2999     * @param top Resource identifier of the top Drawable.
3000     * @param end Resource identifier of the end Drawable.
3001     * @param bottom Resource identifier of the bottom Drawable.
3002     *
3003     * @attr ref android.R.styleable#TextView_drawableStart
3004     * @attr ref android.R.styleable#TextView_drawableTop
3005     * @attr ref android.R.styleable#TextView_drawableEnd
3006     * @attr ref android.R.styleable#TextView_drawableBottom
3007     */
3008    @android.view.RemotableViewMethod
3009    public void setCompoundDrawablesRelativeWithIntrinsicBounds(@DrawableRes int start,
3010            @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) {
3011        final Context context = getContext();
3012        setCompoundDrawablesRelativeWithIntrinsicBounds(
3013                start != 0 ? context.getDrawable(start) : null,
3014                top != 0 ? context.getDrawable(top) : null,
3015                end != 0 ? context.getDrawable(end) : null,
3016                bottom != 0 ? context.getDrawable(bottom) : null);
3017    }
3018
3019    /**
3020     * Sets the Drawables (if any) to appear to the start of, above, to the end
3021     * of, and below the text. Use {@code null} if you do not want a Drawable
3022     * there. The Drawables' bounds will be set to their intrinsic bounds.
3023     * <p>
3024     * Calling this method will overwrite any Drawables previously set using
3025     * {@link #setCompoundDrawables} or related methods.
3026     *
3027     * @attr ref android.R.styleable#TextView_drawableStart
3028     * @attr ref android.R.styleable#TextView_drawableTop
3029     * @attr ref android.R.styleable#TextView_drawableEnd
3030     * @attr ref android.R.styleable#TextView_drawableBottom
3031     */
3032    @android.view.RemotableViewMethod
3033    public void setCompoundDrawablesRelativeWithIntrinsicBounds(@Nullable Drawable start,
3034            @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) {
3035
3036        if (start != null) {
3037            start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
3038        }
3039        if (end != null) {
3040            end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
3041        }
3042        if (top != null) {
3043            top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
3044        }
3045        if (bottom != null) {
3046            bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
3047        }
3048        setCompoundDrawablesRelative(start, top, end, bottom);
3049    }
3050
3051    /**
3052     * Returns drawables for the left, top, right, and bottom borders.
3053     *
3054     * @attr ref android.R.styleable#TextView_drawableLeft
3055     * @attr ref android.R.styleable#TextView_drawableTop
3056     * @attr ref android.R.styleable#TextView_drawableRight
3057     * @attr ref android.R.styleable#TextView_drawableBottom
3058     */
3059    @NonNull
3060    public Drawable[] getCompoundDrawables() {
3061        final Drawables dr = mDrawables;
3062        if (dr != null) {
3063            return dr.mShowing.clone();
3064        } else {
3065            return new Drawable[] { null, null, null, null };
3066        }
3067    }
3068
3069    /**
3070     * Returns drawables for the start, top, end, and bottom borders.
3071     *
3072     * @attr ref android.R.styleable#TextView_drawableStart
3073     * @attr ref android.R.styleable#TextView_drawableTop
3074     * @attr ref android.R.styleable#TextView_drawableEnd
3075     * @attr ref android.R.styleable#TextView_drawableBottom
3076     */
3077    @NonNull
3078    public Drawable[] getCompoundDrawablesRelative() {
3079        final Drawables dr = mDrawables;
3080        if (dr != null) {
3081            return new Drawable[] {
3082                dr.mDrawableStart, dr.mShowing[Drawables.TOP],
3083                dr.mDrawableEnd, dr.mShowing[Drawables.BOTTOM]
3084            };
3085        } else {
3086            return new Drawable[] { null, null, null, null };
3087        }
3088    }
3089
3090    /**
3091     * Sets the size of the padding between the compound drawables and
3092     * the text.
3093     *
3094     * @attr ref android.R.styleable#TextView_drawablePadding
3095     */
3096    @android.view.RemotableViewMethod
3097    public void setCompoundDrawablePadding(int pad) {
3098        Drawables dr = mDrawables;
3099        if (pad == 0) {
3100            if (dr != null) {
3101                dr.mDrawablePadding = pad;
3102            }
3103        } else {
3104            if (dr == null) {
3105                mDrawables = dr = new Drawables(getContext());
3106            }
3107            dr.mDrawablePadding = pad;
3108        }
3109
3110        invalidate();
3111        requestLayout();
3112    }
3113
3114    /**
3115     * Returns the padding between the compound drawables and the text.
3116     *
3117     * @attr ref android.R.styleable#TextView_drawablePadding
3118     */
3119    public int getCompoundDrawablePadding() {
3120        final Drawables dr = mDrawables;
3121        return dr != null ? dr.mDrawablePadding : 0;
3122    }
3123
3124    /**
3125     * Applies a tint to the compound drawables. Does not modify the
3126     * current tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
3127     * <p>
3128     * Subsequent calls to
3129     * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)}
3130     * and related methods will automatically mutate the drawables and apply
3131     * the specified tint and tint mode using
3132     * {@link Drawable#setTintList(ColorStateList)}.
3133     *
3134     * @param tint the tint to apply, may be {@code null} to clear tint
3135     *
3136     * @attr ref android.R.styleable#TextView_drawableTint
3137     * @see #getCompoundDrawableTintList()
3138     * @see Drawable#setTintList(ColorStateList)
3139     */
3140    public void setCompoundDrawableTintList(@Nullable ColorStateList tint) {
3141        if (mDrawables == null) {
3142            mDrawables = new Drawables(getContext());
3143        }
3144        mDrawables.mTintList = tint;
3145        mDrawables.mHasTint = true;
3146
3147        applyCompoundDrawableTint();
3148    }
3149
3150    /**
3151     * @return the tint applied to the compound drawables
3152     * @attr ref android.R.styleable#TextView_drawableTint
3153     * @see #setCompoundDrawableTintList(ColorStateList)
3154     */
3155    public ColorStateList getCompoundDrawableTintList() {
3156        return mDrawables != null ? mDrawables.mTintList : null;
3157    }
3158
3159    /**
3160     * Specifies the blending mode used to apply the tint specified by
3161     * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound
3162     * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}.
3163     *
3164     * @param tintMode the blending mode used to apply the tint, may be
3165     *                 {@code null} to clear tint
3166     * @attr ref android.R.styleable#TextView_drawableTintMode
3167     * @see #setCompoundDrawableTintList(ColorStateList)
3168     * @see Drawable#setTintMode(PorterDuff.Mode)
3169     */
3170    public void setCompoundDrawableTintMode(@Nullable PorterDuff.Mode tintMode) {
3171        if (mDrawables == null) {
3172            mDrawables = new Drawables(getContext());
3173        }
3174        mDrawables.mTintMode = tintMode;
3175        mDrawables.mHasTintMode = true;
3176
3177        applyCompoundDrawableTint();
3178    }
3179
3180    /**
3181     * Returns the blending mode used to apply the tint to the compound
3182     * drawables, if specified.
3183     *
3184     * @return the blending mode used to apply the tint to the compound
3185     *         drawables
3186     * @attr ref android.R.styleable#TextView_drawableTintMode
3187     * @see #setCompoundDrawableTintMode(PorterDuff.Mode)
3188     */
3189    public PorterDuff.Mode getCompoundDrawableTintMode() {
3190        return mDrawables != null ? mDrawables.mTintMode : null;
3191    }
3192
3193    private void applyCompoundDrawableTint() {
3194        if (mDrawables == null) {
3195            return;
3196        }
3197
3198        if (mDrawables.mHasTint || mDrawables.mHasTintMode) {
3199            final ColorStateList tintList = mDrawables.mTintList;
3200            final PorterDuff.Mode tintMode = mDrawables.mTintMode;
3201            final boolean hasTint = mDrawables.mHasTint;
3202            final boolean hasTintMode = mDrawables.mHasTintMode;
3203            final int[] state = getDrawableState();
3204
3205            for (Drawable dr : mDrawables.mShowing) {
3206                if (dr == null) {
3207                    continue;
3208                }
3209
3210                if (dr == mDrawables.mDrawableError) {
3211                    // From a developer's perspective, the error drawable isn't
3212                    // a compound drawable. Don't apply the generic compound
3213                    // drawable tint to it.
3214                    continue;
3215                }
3216
3217                dr.mutate();
3218
3219                if (hasTint) {
3220                    dr.setTintList(tintList);
3221                }
3222
3223                if (hasTintMode) {
3224                    dr.setTintMode(tintMode);
3225                }
3226
3227                // The drawable (or one of its children) may not have been
3228                // stateful before applying the tint, so let's try again.
3229                if (dr.isStateful()) {
3230                    dr.setState(state);
3231                }
3232            }
3233        }
3234    }
3235
3236    @Override
3237    public void setPadding(int left, int top, int right, int bottom) {
3238        if (left != mPaddingLeft
3239                || right != mPaddingRight
3240                || top != mPaddingTop
3241                ||  bottom != mPaddingBottom) {
3242            nullLayouts();
3243        }
3244
3245        // the super call will requestLayout()
3246        super.setPadding(left, top, right, bottom);
3247        invalidate();
3248    }
3249
3250    @Override
3251    public void setPaddingRelative(int start, int top, int end, int bottom) {
3252        if (start != getPaddingStart()
3253                || end != getPaddingEnd()
3254                || top != mPaddingTop
3255                || bottom != mPaddingBottom) {
3256            nullLayouts();
3257        }
3258
3259        // the super call will requestLayout()
3260        super.setPaddingRelative(start, top, end, bottom);
3261        invalidate();
3262    }
3263
3264    /**
3265     * Gets the autolink mask of the text.  See {@link
3266     * android.text.util.Linkify#ALL Linkify.ALL} and peers for
3267     * possible values.
3268     *
3269     * @attr ref android.R.styleable#TextView_autoLink
3270     */
3271    public final int getAutoLinkMask() {
3272        return mAutoLinkMask;
3273    }
3274
3275    /**
3276     * Sets the text appearance from the specified style resource.
3277     * <p>
3278     * Use a framework-defined {@code TextAppearance} style like
3279     * {@link android.R.style#TextAppearance_Material_Body1 @android:style/TextAppearance.Material.Body1}
3280     * or see {@link android.R.styleable#TextAppearance TextAppearance} for the
3281     * set of attributes that can be used in a custom style.
3282     *
3283     * @param resId the resource identifier of the style to apply
3284     * @attr ref android.R.styleable#TextView_textAppearance
3285     */
3286    @SuppressWarnings("deprecation")
3287    public void setTextAppearance(@StyleRes int resId) {
3288        setTextAppearance(mContext, resId);
3289    }
3290
3291    /**
3292     * Sets the text color, size, style, hint color, and highlight color
3293     * from the specified TextAppearance resource.
3294     *
3295     * @deprecated Use {@link #setTextAppearance(int)} instead.
3296     */
3297    @Deprecated
3298    public void setTextAppearance(Context context, @StyleRes int resId) {
3299        final TypedArray ta = context.obtainStyledAttributes(resId, R.styleable.TextAppearance);
3300
3301        final int textColorHighlight = ta.getColor(
3302                R.styleable.TextAppearance_textColorHighlight, 0);
3303        if (textColorHighlight != 0) {
3304            setHighlightColor(textColorHighlight);
3305        }
3306
3307        final ColorStateList textColor = ta.getColorStateList(R.styleable.TextAppearance_textColor);
3308        if (textColor != null) {
3309            setTextColor(textColor);
3310        }
3311
3312        final int textSize = ta.getDimensionPixelSize(R.styleable.TextAppearance_textSize, 0);
3313        if (textSize != 0) {
3314            setRawTextSize(textSize);
3315        }
3316
3317        final ColorStateList textColorHint = ta.getColorStateList(
3318                R.styleable.TextAppearance_textColorHint);
3319        if (textColorHint != null) {
3320            setHintTextColor(textColorHint);
3321        }
3322
3323        final ColorStateList textColorLink = ta.getColorStateList(
3324                R.styleable.TextAppearance_textColorLink);
3325        if (textColorLink != null) {
3326            setLinkTextColor(textColorLink);
3327        }
3328
3329        Typeface fontTypeface = null;
3330        String fontFamily = null;
3331        try {
3332            fontTypeface = ta.getFont(R.styleable.TextAppearance_fontFamily);
3333        } catch (UnsupportedOperationException | Resources.NotFoundException e) {
3334            // Expected if it is not a font resource.
3335        }
3336        if (fontTypeface == null) {
3337            fontFamily = ta.getString(R.styleable.TextAppearance_fontFamily);
3338        }
3339        final int typefaceIndex = ta.getInt(R.styleable.TextAppearance_typeface, -1);
3340        final int styleIndex = ta.getInt(R.styleable.TextAppearance_textStyle, -1);
3341        setTypefaceFromAttrs(fontTypeface, fontFamily, typefaceIndex, styleIndex);
3342
3343        final int shadowColor = ta.getInt(R.styleable.TextAppearance_shadowColor, 0);
3344        if (shadowColor != 0) {
3345            final float dx = ta.getFloat(R.styleable.TextAppearance_shadowDx, 0);
3346            final float dy = ta.getFloat(R.styleable.TextAppearance_shadowDy, 0);
3347            final float r = ta.getFloat(R.styleable.TextAppearance_shadowRadius, 0);
3348            setShadowLayer(r, dx, dy, shadowColor);
3349        }
3350
3351        if (ta.getBoolean(R.styleable.TextAppearance_textAllCaps, false)) {
3352            setTransformationMethod(new AllCapsTransformationMethod(getContext()));
3353        }
3354
3355        if (ta.hasValue(R.styleable.TextAppearance_elegantTextHeight)) {
3356            setElegantTextHeight(ta.getBoolean(
3357                    R.styleable.TextAppearance_elegantTextHeight, false));
3358        }
3359
3360        if (ta.hasValue(R.styleable.TextAppearance_letterSpacing)) {
3361            setLetterSpacing(ta.getFloat(
3362                    R.styleable.TextAppearance_letterSpacing, 0));
3363        }
3364
3365        if (ta.hasValue(R.styleable.TextAppearance_fontFeatureSettings)) {
3366            setFontFeatureSettings(ta.getString(
3367                    R.styleable.TextAppearance_fontFeatureSettings));
3368        }
3369
3370        ta.recycle();
3371    }
3372
3373    /**
3374     * Get the default primary {@link Locale} of the text in this TextView. This will always be
3375     * the first member of {@link #getTextLocales()}.
3376     * @return the default primary {@link Locale} of the text in this TextView.
3377     */
3378    @NonNull
3379    public Locale getTextLocale() {
3380        return mTextPaint.getTextLocale();
3381    }
3382
3383    /**
3384     * Get the default {@link LocaleList} of the text in this TextView.
3385     * @return the default {@link LocaleList} of the text in this TextView.
3386     */
3387    @NonNull @Size(min = 1)
3388    public LocaleList getTextLocales() {
3389        return mTextPaint.getTextLocales();
3390    }
3391
3392    /**
3393     * Set the default {@link LocaleList} of the text in this TextView to a one-member list
3394     * containing just the given value.
3395     *
3396     * @param locale the {@link Locale} for drawing text, must not be null.
3397     *
3398     * @see #setTextLocales
3399     */
3400    public void setTextLocale(@NonNull Locale locale) {
3401        mLocalesChanged = true;
3402        mTextPaint.setTextLocale(locale);
3403        if (mLayout != null) {
3404            nullLayouts();
3405            requestLayout();
3406            invalidate();
3407        }
3408    }
3409
3410    /**
3411     * Set the default {@link LocaleList} of the text in this TextView to the given value.
3412     *
3413     * This value is used to choose appropriate typefaces for ambiguous characters (typically used
3414     * for CJK locales to disambiguate Hanzi/Kanji/Hanja characters). It also affects
3415     * other aspects of text display, including line breaking.
3416     *
3417     * @param locales the {@link LocaleList} for drawing text, must not be null or empty.
3418     *
3419     * @see Paint#setTextLocales
3420     */
3421    public void setTextLocales(@NonNull @Size(min = 1) LocaleList locales) {
3422        mLocalesChanged = true;
3423        mTextPaint.setTextLocales(locales);
3424        if (mLayout != null) {
3425            nullLayouts();
3426            requestLayout();
3427            invalidate();
3428        }
3429    }
3430
3431    @Override
3432    protected void onConfigurationChanged(Configuration newConfig) {
3433        super.onConfigurationChanged(newConfig);
3434        if (!mLocalesChanged) {
3435            mTextPaint.setTextLocales(LocaleList.getDefault());
3436            if (mLayout != null) {
3437                nullLayouts();
3438                requestLayout();
3439                invalidate();
3440            }
3441        }
3442    }
3443
3444    /**
3445     * @return the size (in pixels) of the default text size in this TextView.
3446     */
3447    @ViewDebug.ExportedProperty(category = "text")
3448    public float getTextSize() {
3449        return mTextPaint.getTextSize();
3450    }
3451
3452    /**
3453     * @return the size (in scaled pixels) of thee default text size in this TextView.
3454     * @hide
3455     */
3456    @ViewDebug.ExportedProperty(category = "text")
3457    public float getScaledTextSize() {
3458        return mTextPaint.getTextSize() / mTextPaint.density;
3459    }
3460
3461    /** @hide */
3462    @ViewDebug.ExportedProperty(category = "text", mapping = {
3463            @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"),
3464            @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"),
3465            @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"),
3466            @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC")
3467    })
3468    public int getTypefaceStyle() {
3469        Typeface typeface = mTextPaint.getTypeface();
3470        return typeface != null ? typeface.getStyle() : Typeface.NORMAL;
3471    }
3472
3473    /**
3474     * Set the default text size to the given value, interpreted as "scaled
3475     * pixel" units.  This size is adjusted based on the current density and
3476     * user font size preference.
3477     *
3478     * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op.
3479     *
3480     * @param size The scaled pixel size.
3481     *
3482     * @attr ref android.R.styleable#TextView_textSize
3483     */
3484    @android.view.RemotableViewMethod
3485    public void setTextSize(float size) {
3486        setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
3487    }
3488
3489    /**
3490     * Set the default text size to a given unit and value. See {@link
3491     * TypedValue} for the possible dimension units.
3492     *
3493     * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op.
3494     *
3495     * @param unit The desired dimension unit.
3496     * @param size The desired size in the given units.
3497     *
3498     * @attr ref android.R.styleable#TextView_textSize
3499     */
3500    public void setTextSize(int unit, float size) {
3501        if (!isAutoSizeEnabled()) {
3502            setTextSizeInternal(unit, size);
3503        }
3504    }
3505
3506    private void setTextSizeInternal(int unit, float size) {
3507        Context c = getContext();
3508        Resources r;
3509
3510        if (c == null) {
3511            r = Resources.getSystem();
3512        } else {
3513            r = c.getResources();
3514        }
3515
3516        setRawTextSize(TypedValue.applyDimension(
3517                unit, size, r.getDisplayMetrics()));
3518    }
3519
3520    private void setRawTextSize(float size) {
3521        if (size != mTextPaint.getTextSize()) {
3522            mTextPaint.setTextSize(size);
3523
3524            if (mLayout != null) {
3525                // Do not auto-size right after setting the text size.
3526                mNeedsAutoSizeText = false;
3527                nullLayouts();
3528                requestLayout();
3529                invalidate();
3530            }
3531        }
3532    }
3533
3534    /**
3535     * @return the extent by which text is currently being stretched
3536     * horizontally.  This will usually be 1.
3537     */
3538    public float getTextScaleX() {
3539        return mTextPaint.getTextScaleX();
3540    }
3541
3542    /**
3543     * Sets the extent by which text should be stretched horizontally.
3544     *
3545     * @attr ref android.R.styleable#TextView_textScaleX
3546     */
3547    @android.view.RemotableViewMethod
3548    public void setTextScaleX(float size) {
3549        if (size != mTextPaint.getTextScaleX()) {
3550            mUserSetTextScaleX = true;
3551            mTextPaint.setTextScaleX(size);
3552
3553            if (mLayout != null) {
3554                nullLayouts();
3555                requestLayout();
3556                invalidate();
3557            }
3558        }
3559    }
3560
3561    /**
3562     * Sets the typeface and style in which the text should be displayed.
3563     * Note that not all Typeface families actually have bold and italic
3564     * variants, so you may need to use
3565     * {@link #setTypeface(Typeface, int)} to get the appearance
3566     * that you actually want.
3567     *
3568     * @see #getTypeface()
3569     *
3570     * @attr ref android.R.styleable#TextView_fontFamily
3571     * @attr ref android.R.styleable#TextView_typeface
3572     * @attr ref android.R.styleable#TextView_textStyle
3573     */
3574    public void setTypeface(Typeface tf) {
3575        if (mTextPaint.getTypeface() != tf) {
3576            mTextPaint.setTypeface(tf);
3577
3578            if (mLayout != null) {
3579                nullLayouts();
3580                requestLayout();
3581                invalidate();
3582            }
3583        }
3584    }
3585
3586    /**
3587     * @return the current typeface and style in which the text is being
3588     * displayed.
3589     *
3590     * @see #setTypeface(Typeface)
3591     *
3592     * @attr ref android.R.styleable#TextView_fontFamily
3593     * @attr ref android.R.styleable#TextView_typeface
3594     * @attr ref android.R.styleable#TextView_textStyle
3595     */
3596    public Typeface getTypeface() {
3597        return mTextPaint.getTypeface();
3598    }
3599
3600    /**
3601     * Set the TextView's elegant height metrics flag. This setting selects font
3602     * variants that have not been compacted to fit Latin-based vertical
3603     * metrics, and also increases top and bottom bounds to provide more space.
3604     *
3605     * @param elegant set the paint's elegant metrics flag.
3606     *
3607     * @attr ref android.R.styleable#TextView_elegantTextHeight
3608     */
3609    public void setElegantTextHeight(boolean elegant) {
3610        if (elegant != mTextPaint.isElegantTextHeight()) {
3611            mTextPaint.setElegantTextHeight(elegant);
3612            if (mLayout != null) {
3613                nullLayouts();
3614                requestLayout();
3615                invalidate();
3616            }
3617        }
3618    }
3619
3620    /**
3621     * @return the extent by which text is currently being letter-spaced.
3622     * This will normally be 0.
3623     *
3624     * @see #setLetterSpacing(float)
3625     * @see Paint#setLetterSpacing
3626     */
3627    public float getLetterSpacing() {
3628        return mTextPaint.getLetterSpacing();
3629    }
3630
3631    /**
3632     * Sets text letter-spacing.  The value is in 'EM' units.  Typical values
3633     * for slight expansion will be around 0.05.  Negative values tighten text.
3634     *
3635     * @see #getLetterSpacing()
3636     * @see Paint#getLetterSpacing
3637     *
3638     * @attr ref android.R.styleable#TextView_letterSpacing
3639     */
3640    @android.view.RemotableViewMethod
3641    public void setLetterSpacing(float letterSpacing) {
3642        if (letterSpacing != mTextPaint.getLetterSpacing()) {
3643            mTextPaint.setLetterSpacing(letterSpacing);
3644
3645            if (mLayout != null) {
3646                nullLayouts();
3647                requestLayout();
3648                invalidate();
3649            }
3650        }
3651    }
3652
3653    /**
3654     * Returns the font feature settings. The format is the same as the CSS
3655     * font-feature-settings attribute:
3656     * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
3657     *     https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a>
3658     *
3659     * @return the currently set font feature settings.  Default is null.
3660     *
3661     * @see #setFontFeatureSettings(String)
3662     * @see Paint#setFontFeatureSettings(String) Paint.setFontFeatureSettings(String)
3663     */
3664    @Nullable
3665    public String getFontFeatureSettings() {
3666        return mTextPaint.getFontFeatureSettings();
3667    }
3668
3669    /**
3670     * Returns the font variation settings.
3671     *
3672     * @return the currently set font variation settings.  Returns null if no variation is
3673     * specified.
3674     *
3675     * @see #setFontVariationSettings(String)
3676     * @see Paint#setFontVariationSettings(String) Paint.setFontVariationSettings(String)
3677     */
3678    @Nullable
3679    public String getFontVariationSettings() {
3680        return mTextPaint.getFontVariationSettings();
3681    }
3682
3683    /**
3684     * Sets the break strategy for breaking paragraphs into lines. The default value for
3685     * TextView is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}, and the default value for
3686     * EditText is {@link Layout#BREAK_STRATEGY_SIMPLE}, the latter to avoid the
3687     * text "dancing" when being edited.
3688     *
3689     * @attr ref android.R.styleable#TextView_breakStrategy
3690     * @see #getBreakStrategy()
3691     */
3692    public void setBreakStrategy(@Layout.BreakStrategy int breakStrategy) {
3693        mBreakStrategy = breakStrategy;
3694        if (mLayout != null) {
3695            nullLayouts();
3696            requestLayout();
3697            invalidate();
3698        }
3699    }
3700
3701    /**
3702     * @return the currently set break strategy.
3703     *
3704     * @attr ref android.R.styleable#TextView_breakStrategy
3705     * @see #setBreakStrategy(int)
3706     */
3707    @Layout.BreakStrategy
3708    public int getBreakStrategy() {
3709        return mBreakStrategy;
3710    }
3711
3712    /**
3713     * Sets the hyphenation frequency. The default value for both TextView and EditText, which is
3714     * set from the theme, is {@link Layout#HYPHENATION_FREQUENCY_NORMAL}.
3715     *
3716     * @attr ref android.R.styleable#TextView_hyphenationFrequency
3717     * @see #getHyphenationFrequency()
3718     */
3719    public void setHyphenationFrequency(@Layout.HyphenationFrequency int hyphenationFrequency) {
3720        mHyphenationFrequency = hyphenationFrequency;
3721        if (mLayout != null) {
3722            nullLayouts();
3723            requestLayout();
3724            invalidate();
3725        }
3726    }
3727
3728    /**
3729     * @return the currently set hyphenation frequency.
3730     *
3731     * @attr ref android.R.styleable#TextView_hyphenationFrequency
3732     * @see #setHyphenationFrequency(int)
3733     */
3734    @Layout.HyphenationFrequency
3735    public int getHyphenationFrequency() {
3736        return mHyphenationFrequency;
3737    }
3738
3739    /**
3740     * Enables or disables full justification. The default value is false.
3741     *
3742     * @see #getJustify()
3743     */
3744    public void setJustify(boolean justify) {
3745        mJustify = justify;
3746        if (mLayout != null) {
3747            nullLayouts();
3748            requestLayout();
3749            invalidate();
3750        }
3751    }
3752
3753    /**
3754     * @return true if currently paragraph justification is enabled.
3755     *
3756     * @see #setJustify(boolean)
3757     */
3758    public boolean getJustify() {
3759        return mJustify;
3760    }
3761
3762    /**
3763     * Sets font feature settings. The format is the same as the CSS
3764     * font-feature-settings attribute:
3765     * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
3766     *     https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a>
3767     *
3768     * @param fontFeatureSettings font feature settings represented as CSS compatible string
3769     *
3770     * @see #getFontFeatureSettings()
3771     * @see Paint#getFontFeatureSettings() Paint.getFontFeatureSettings()
3772     *
3773     * @attr ref android.R.styleable#TextView_fontFeatureSettings
3774     */
3775    @android.view.RemotableViewMethod
3776    public void setFontFeatureSettings(@Nullable String fontFeatureSettings) {
3777        if (fontFeatureSettings != mTextPaint.getFontFeatureSettings()) {
3778            mTextPaint.setFontFeatureSettings(fontFeatureSettings);
3779
3780            if (mLayout != null) {
3781                nullLayouts();
3782                requestLayout();
3783                invalidate();
3784            }
3785        }
3786    }
3787
3788
3789    /**
3790     * Sets TrueType or OpenType font variation settings. The settings string is constructed from
3791     * multiple pairs of axis tag and style values. The axis tag must contain four ASCII characters
3792     * and must be wrapped with single quotes (U+0027) or double quotes (U+0022). Axis strings that
3793     * are longer or shorter than four characters, or contain characters outside of U+0020..U+007E
3794     * are invalid. If a specified axis name is not defined in the font, the settings will be
3795     * ignored.
3796     *
3797     * <pre>
3798     *   textView.setFontVariationSettings("'wdth' 1.0");
3799     *   textView.setFontVariationSettings("'AX  ' 1.8, 'FB  ' 2.0");
3800     * </pre>
3801     *
3802     * @param fontVariationSettings font variation settings. You can pass null or empty string as
3803     *                              no variation settings.
3804     *
3805     * @see #getFontVariationSettings()
3806     * @see Paint#getFontVariationSettings() Paint.getFontVariationSettings()
3807     */
3808    public void setFontVariationSettings(@Nullable String fontVariationSettings) {
3809        final String existingSettings = mTextPaint.getFontVariationSettings();
3810        if (fontVariationSettings == existingSettings
3811                || (fontVariationSettings != null
3812                        && fontVariationSettings.equals(existingSettings))) {
3813            return;
3814        }
3815        mTextPaint.setFontVariationSettings(fontVariationSettings);
3816
3817        if (mLayout != null) {
3818            nullLayouts();
3819            requestLayout();
3820            invalidate();
3821        }
3822    }
3823
3824    /**
3825     * Sets the text color for all the states (normal, selected,
3826     * focused) to be this color.
3827     *
3828     * @param color A color value in the form 0xAARRGGBB.
3829     * Do not pass a resource ID. To get a color value from a resource ID, call
3830     * {@link android.support.v4.content.ContextCompat#getColor(Context, int) getColor}.
3831     *
3832     * @see #setTextColor(ColorStateList)
3833     * @see #getTextColors()
3834     *
3835     * @attr ref android.R.styleable#TextView_textColor
3836     */
3837    @android.view.RemotableViewMethod
3838    public void setTextColor(@ColorInt int color) {
3839        mTextColor = ColorStateList.valueOf(color);
3840        updateTextColors();
3841    }
3842
3843    /**
3844     * Sets the text color.
3845     *
3846     * @see #setTextColor(int)
3847     * @see #getTextColors()
3848     * @see #setHintTextColor(ColorStateList)
3849     * @see #setLinkTextColor(ColorStateList)
3850     *
3851     * @attr ref android.R.styleable#TextView_textColor
3852     */
3853    @android.view.RemotableViewMethod
3854    public void setTextColor(ColorStateList colors) {
3855        if (colors == null) {
3856            throw new NullPointerException();
3857        }
3858
3859        mTextColor = colors;
3860        updateTextColors();
3861    }
3862
3863    /**
3864     * Gets the text colors for the different states (normal, selected, focused) of the TextView.
3865     *
3866     * @see #setTextColor(ColorStateList)
3867     * @see #setTextColor(int)
3868     *
3869     * @attr ref android.R.styleable#TextView_textColor
3870     */
3871    public final ColorStateList getTextColors() {
3872        return mTextColor;
3873    }
3874
3875    /**
3876     * <p>Return the current color selected for normal text.</p>
3877     *
3878     * @return Returns the current text color.
3879     */
3880    @ColorInt
3881    public final int getCurrentTextColor() {
3882        return mCurTextColor;
3883    }
3884
3885    /**
3886     * Sets the color used to display the selection highlight.
3887     *
3888     * @attr ref android.R.styleable#TextView_textColorHighlight
3889     */
3890    @android.view.RemotableViewMethod
3891    public void setHighlightColor(@ColorInt int color) {
3892        if (mHighlightColor != color) {
3893            mHighlightColor = color;
3894            invalidate();
3895        }
3896    }
3897
3898    /**
3899     * @return the color used to display the selection highlight
3900     *
3901     * @see #setHighlightColor(int)
3902     *
3903     * @attr ref android.R.styleable#TextView_textColorHighlight
3904     */
3905    @ColorInt
3906    public int getHighlightColor() {
3907        return mHighlightColor;
3908    }
3909
3910    /**
3911     * Sets whether the soft input method will be made visible when this
3912     * TextView gets focused. The default is true.
3913     */
3914    @android.view.RemotableViewMethod
3915    public final void setShowSoftInputOnFocus(boolean show) {
3916        createEditorIfNeeded();
3917        mEditor.mShowSoftInputOnFocus = show;
3918    }
3919
3920    /**
3921     * Returns whether the soft input method will be made visible when this
3922     * TextView gets focused. The default is true.
3923     */
3924    public final boolean getShowSoftInputOnFocus() {
3925        // When there is no Editor, return default true value
3926        return mEditor == null || mEditor.mShowSoftInputOnFocus;
3927    }
3928
3929    /**
3930     * Gives the text a shadow of the specified blur radius and color, the specified
3931     * distance from its drawn position.
3932     * <p>
3933     * The text shadow produced does not interact with the properties on view
3934     * that are responsible for real time shadows,
3935     * {@link View#getElevation() elevation} and
3936     * {@link View#getTranslationZ() translationZ}.
3937     *
3938     * @see Paint#setShadowLayer(float, float, float, int)
3939     *
3940     * @attr ref android.R.styleable#TextView_shadowColor
3941     * @attr ref android.R.styleable#TextView_shadowDx
3942     * @attr ref android.R.styleable#TextView_shadowDy
3943     * @attr ref android.R.styleable#TextView_shadowRadius
3944     */
3945    public void setShadowLayer(float radius, float dx, float dy, int color) {
3946        mTextPaint.setShadowLayer(radius, dx, dy, color);
3947
3948        mShadowRadius = radius;
3949        mShadowDx = dx;
3950        mShadowDy = dy;
3951        mShadowColor = color;
3952
3953        // Will change text clip region
3954        if (mEditor != null) {
3955            mEditor.invalidateTextDisplayList();
3956            mEditor.invalidateHandlesAndActionMode();
3957        }
3958        invalidate();
3959    }
3960
3961    /**
3962     * Gets the radius of the shadow layer.
3963     *
3964     * @return the radius of the shadow layer. If 0, the shadow layer is not visible
3965     *
3966     * @see #setShadowLayer(float, float, float, int)
3967     *
3968     * @attr ref android.R.styleable#TextView_shadowRadius
3969     */
3970    public float getShadowRadius() {
3971        return mShadowRadius;
3972    }
3973
3974    /**
3975     * @return the horizontal offset of the shadow layer
3976     *
3977     * @see #setShadowLayer(float, float, float, int)
3978     *
3979     * @attr ref android.R.styleable#TextView_shadowDx
3980     */
3981    public float getShadowDx() {
3982        return mShadowDx;
3983    }
3984
3985    /**
3986     * @return the vertical offset of the shadow layer
3987     *
3988     * @see #setShadowLayer(float, float, float, int)
3989     *
3990     * @attr ref android.R.styleable#TextView_shadowDy
3991     */
3992    public float getShadowDy() {
3993        return mShadowDy;
3994    }
3995
3996    /**
3997     * @return the color of the shadow layer
3998     *
3999     * @see #setShadowLayer(float, float, float, int)
4000     *
4001     * @attr ref android.R.styleable#TextView_shadowColor
4002     */
4003    @ColorInt
4004    public int getShadowColor() {
4005        return mShadowColor;
4006    }
4007
4008    /**
4009     * @return the base paint used for the text.  Please use this only to
4010     * consult the Paint's properties and not to change them.
4011     */
4012    public TextPaint getPaint() {
4013        return mTextPaint;
4014    }
4015
4016    /**
4017     * Sets the autolink mask of the text.  See {@link
4018     * android.text.util.Linkify#ALL Linkify.ALL} and peers for
4019     * possible values.
4020     *
4021     * @attr ref android.R.styleable#TextView_autoLink
4022     */
4023    @android.view.RemotableViewMethod
4024    public final void setAutoLinkMask(int mask) {
4025        mAutoLinkMask = mask;
4026    }
4027
4028    /**
4029     * Sets whether the movement method will automatically be set to
4030     * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
4031     * set to nonzero and links are detected in {@link #setText}.
4032     * The default is true.
4033     *
4034     * @attr ref android.R.styleable#TextView_linksClickable
4035     */
4036    @android.view.RemotableViewMethod
4037    public final void setLinksClickable(boolean whether) {
4038        mLinksClickable = whether;
4039    }
4040
4041    /**
4042     * Returns whether the movement method will automatically be set to
4043     * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
4044     * set to nonzero and links are detected in {@link #setText}.
4045     * The default is true.
4046     *
4047     * @attr ref android.R.styleable#TextView_linksClickable
4048     */
4049    public final boolean getLinksClickable() {
4050        return mLinksClickable;
4051    }
4052
4053    /**
4054     * Returns the list of URLSpans attached to the text
4055     * (by {@link Linkify} or otherwise) if any.  You can call
4056     * {@link URLSpan#getURL} on them to find where they link to
4057     * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
4058     * to find the region of the text they are attached to.
4059     */
4060    public URLSpan[] getUrls() {
4061        if (mText instanceof Spanned) {
4062            return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
4063        } else {
4064            return new URLSpan[0];
4065        }
4066    }
4067
4068    /**
4069     * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this
4070     * TextView.
4071     *
4072     * @see #setHintTextColor(ColorStateList)
4073     * @see #getHintTextColors()
4074     * @see #setTextColor(int)
4075     *
4076     * @attr ref android.R.styleable#TextView_textColorHint
4077     */
4078    @android.view.RemotableViewMethod
4079    public final void setHintTextColor(@ColorInt int color) {
4080        mHintTextColor = ColorStateList.valueOf(color);
4081        updateTextColors();
4082    }
4083
4084    /**
4085     * Sets the color of the hint text.
4086     *
4087     * @see #getHintTextColors()
4088     * @see #setHintTextColor(int)
4089     * @see #setTextColor(ColorStateList)
4090     * @see #setLinkTextColor(ColorStateList)
4091     *
4092     * @attr ref android.R.styleable#TextView_textColorHint
4093     */
4094    public final void setHintTextColor(ColorStateList colors) {
4095        mHintTextColor = colors;
4096        updateTextColors();
4097    }
4098
4099    /**
4100     * @return the color of the hint text, for the different states of this TextView.
4101     *
4102     * @see #setHintTextColor(ColorStateList)
4103     * @see #setHintTextColor(int)
4104     * @see #setTextColor(ColorStateList)
4105     * @see #setLinkTextColor(ColorStateList)
4106     *
4107     * @attr ref android.R.styleable#TextView_textColorHint
4108     */
4109    public final ColorStateList getHintTextColors() {
4110        return mHintTextColor;
4111    }
4112
4113    /**
4114     * <p>Return the current color selected to paint the hint text.</p>
4115     *
4116     * @return Returns the current hint text color.
4117     */
4118    @ColorInt
4119    public final int getCurrentHintTextColor() {
4120        return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
4121    }
4122
4123    /**
4124     * Sets the color of links in the text.
4125     *
4126     * @see #setLinkTextColor(ColorStateList)
4127     * @see #getLinkTextColors()
4128     *
4129     * @attr ref android.R.styleable#TextView_textColorLink
4130     */
4131    @android.view.RemotableViewMethod
4132    public final void setLinkTextColor(@ColorInt int color) {
4133        mLinkTextColor = ColorStateList.valueOf(color);
4134        updateTextColors();
4135    }
4136
4137    /**
4138     * Sets the color of links in the text.
4139     *
4140     * @see #setLinkTextColor(int)
4141     * @see #getLinkTextColors()
4142     * @see #setTextColor(ColorStateList)
4143     * @see #setHintTextColor(ColorStateList)
4144     *
4145     * @attr ref android.R.styleable#TextView_textColorLink
4146     */
4147    public final void setLinkTextColor(ColorStateList colors) {
4148        mLinkTextColor = colors;
4149        updateTextColors();
4150    }
4151
4152    /**
4153     * @return the list of colors used to paint the links in the text, for the different states of
4154     * this TextView
4155     *
4156     * @see #setLinkTextColor(ColorStateList)
4157     * @see #setLinkTextColor(int)
4158     *
4159     * @attr ref android.R.styleable#TextView_textColorLink
4160     */
4161    public final ColorStateList getLinkTextColors() {
4162        return mLinkTextColor;
4163    }
4164
4165    /**
4166     * Sets the horizontal alignment of the text and the
4167     * vertical gravity that will be used when there is extra space
4168     * in the TextView beyond what is required for the text itself.
4169     *
4170     * @see android.view.Gravity
4171     * @attr ref android.R.styleable#TextView_gravity
4172     */
4173    public void setGravity(int gravity) {
4174        if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
4175            gravity |= Gravity.START;
4176        }
4177        if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
4178            gravity |= Gravity.TOP;
4179        }
4180
4181        boolean newLayout = false;
4182
4183        if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)
4184                != (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) {
4185            newLayout = true;
4186        }
4187
4188        if (gravity != mGravity) {
4189            invalidate();
4190        }
4191
4192        mGravity = gravity;
4193
4194        if (mLayout != null && newLayout) {
4195            // XXX this is heavy-handed because no actual content changes.
4196            int want = mLayout.getWidth();
4197            int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
4198
4199            makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
4200                    mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), true);
4201        }
4202    }
4203
4204    /**
4205     * Returns the horizontal and vertical alignment of this TextView.
4206     *
4207     * @see android.view.Gravity
4208     * @attr ref android.R.styleable#TextView_gravity
4209     */
4210    public int getGravity() {
4211        return mGravity;
4212    }
4213
4214    /**
4215     * @return the flags on the Paint being used to display the text.
4216     * @see Paint#getFlags
4217     */
4218    public int getPaintFlags() {
4219        return mTextPaint.getFlags();
4220    }
4221
4222    /**
4223     * Sets flags on the Paint being used to display the text and
4224     * reflows the text if they are different from the old flags.
4225     * @see Paint#setFlags
4226     */
4227    @android.view.RemotableViewMethod
4228    public void setPaintFlags(int flags) {
4229        if (mTextPaint.getFlags() != flags) {
4230            mTextPaint.setFlags(flags);
4231
4232            if (mLayout != null) {
4233                nullLayouts();
4234                requestLayout();
4235                invalidate();
4236            }
4237        }
4238    }
4239
4240    /**
4241     * Sets whether the text should be allowed to be wider than the
4242     * View is.  If false, it will be wrapped to the width of the View.
4243     *
4244     * @attr ref android.R.styleable#TextView_scrollHorizontally
4245     */
4246    public void setHorizontallyScrolling(boolean whether) {
4247        if (mHorizontallyScrolling != whether) {
4248            mHorizontallyScrolling = whether;
4249
4250            if (mLayout != null) {
4251                nullLayouts();
4252                requestLayout();
4253                invalidate();
4254            }
4255        }
4256    }
4257
4258    /**
4259     * Returns whether the text is allowed to be wider than the View is.
4260     * If false, the text will be wrapped to the width of the View.
4261     *
4262     * @attr ref android.R.styleable#TextView_scrollHorizontally
4263     * @hide
4264     */
4265    public boolean getHorizontallyScrolling() {
4266        return mHorizontallyScrolling;
4267    }
4268
4269    /**
4270     * Sets the height of the TextView to be at least {@code minLines} tall.
4271     * <p>
4272     * This value is used for height calculation if LayoutParams does not force TextView to have an
4273     * exact height. Setting this value overrides other previous minimum height configurations such
4274     * as {@link #setMinHeight(int)} or {@link #setHeight(int)}. {@link #setSingleLine()} will set
4275     * this value to 1.
4276     *
4277     * @param minLines the minimum height of TextView in terms of number of lines
4278     *
4279     * @see #getMinLines()
4280     * @see #setLines(int)
4281     *
4282     * @attr ref android.R.styleable#TextView_minLines
4283     */
4284    @android.view.RemotableViewMethod
4285    public void setMinLines(int minLines) {
4286        mMinimum = minLines;
4287        mMinMode = LINES;
4288
4289        requestLayout();
4290        invalidate();
4291    }
4292
4293    /**
4294     * Returns the minimum height of TextView in terms of number of lines or -1 if the minimum
4295     * height was set using {@link #setMinHeight(int)} or {@link #setHeight(int)}.
4296     *
4297     * @return the minimum height of TextView in terms of number of lines or -1 if the minimum
4298     *         height is not defined in lines
4299     *
4300     * @see #setMinLines(int)
4301     * @see #setLines(int)
4302     *
4303     * @attr ref android.R.styleable#TextView_minLines
4304     */
4305    public int getMinLines() {
4306        return mMinMode == LINES ? mMinimum : -1;
4307    }
4308
4309    /**
4310     * Sets the height of the TextView to be at least {@code minPixels} tall.
4311     * <p>
4312     * This value is used for height calculation if LayoutParams does not force TextView to have an
4313     * exact height. Setting this value overrides previous minimum height configurations such as
4314     * {@link #setMinLines(int)} or {@link #setLines(int)}.
4315     * <p>
4316     * The value given here is different than {@link #setMinimumHeight(int)}. Between
4317     * {@code minHeight} and the value set in {@link #setMinimumHeight(int)}, the greater one is
4318     * used to decide the final height.
4319     *
4320     * @param minPixels the minimum height of TextView in terms of pixels
4321     *
4322     * @see #getMinHeight()
4323     * @see #setHeight(int)
4324     *
4325     * @attr ref android.R.styleable#TextView_minHeight
4326     */
4327    @android.view.RemotableViewMethod
4328    public void setMinHeight(int minPixels) {
4329        mMinimum = minPixels;
4330        mMinMode = PIXELS;
4331
4332        requestLayout();
4333        invalidate();
4334    }
4335
4336    /**
4337     * Returns the minimum height of TextView in terms of pixels or -1 if the minimum height was
4338     * set using {@link #setMinLines(int)} or {@link #setLines(int)}.
4339     *
4340     * @return the minimum height of TextView in terms of pixels or -1 if the minimum height is not
4341     *         defined in pixels
4342     *
4343     * @see #setMinHeight(int)
4344     * @see #setHeight(int)
4345     *
4346     * @attr ref android.R.styleable#TextView_minHeight
4347     */
4348    public int getMinHeight() {
4349        return mMinMode == PIXELS ? mMinimum : -1;
4350    }
4351
4352    /**
4353     * Sets the height of the TextView to be at most {@code maxLines} tall.
4354     * <p>
4355     * This value is used for height calculation if LayoutParams does not force TextView to have an
4356     * exact height. Setting this value overrides previous maximum height configurations such as
4357     * {@link #setMaxHeight(int)} or {@link #setLines(int)}.
4358     *
4359     * @param maxLines the maximum height of TextView in terms of number of lines
4360     *
4361     * @see #getMaxLines()
4362     * @see #setLines(int)
4363     *
4364     * @attr ref android.R.styleable#TextView_maxLines
4365     */
4366    @android.view.RemotableViewMethod
4367    public void setMaxLines(int maxLines) {
4368        mMaximum = maxLines;
4369        mMaxMode = LINES;
4370
4371        requestLayout();
4372        invalidate();
4373    }
4374
4375    /**
4376     * Returns the maximum height of TextView in terms of number of lines or -1 if the
4377     * maximum height was set using {@link #setMaxHeight(int)} or {@link #setHeight(int)}.
4378     *
4379     * @return the maximum height of TextView in terms of number of lines. -1 if the maximum height
4380     *         is not defined in lines.
4381     *
4382     * @see #setMaxLines(int)
4383     * @see #setLines(int)
4384     *
4385     * @attr ref android.R.styleable#TextView_maxLines
4386     */
4387    public int getMaxLines() {
4388        return mMaxMode == LINES ? mMaximum : -1;
4389    }
4390
4391    /**
4392     * Sets the height of the TextView to be at most {@code maxPixels} tall.
4393     * <p>
4394     * This value is used for height calculation if LayoutParams does not force TextView to have an
4395     * exact height. Setting this value overrides previous maximum height configurations such as
4396     * {@link #setMaxLines(int)} or {@link #setLines(int)}.
4397     *
4398     * @param maxPixels the maximum height of TextView in terms of pixels
4399     *
4400     * @see #getMaxHeight()
4401     * @see #setHeight(int)
4402     *
4403     * @attr ref android.R.styleable#TextView_maxHeight
4404     */
4405    @android.view.RemotableViewMethod
4406    public void setMaxHeight(int maxPixels) {
4407        mMaximum = maxPixels;
4408        mMaxMode = PIXELS;
4409
4410        requestLayout();
4411        invalidate();
4412    }
4413
4414    /**
4415     * Returns the maximum height of TextView in terms of pixels or -1 if the maximum height was
4416     * set using {@link #setMaxLines(int)} or {@link #setLines(int)}.
4417     *
4418     * @return the maximum height of TextView in terms of pixels or -1 if the maximum height
4419     *         is not defined in pixels
4420     *
4421     * @see #setMaxHeight(int)
4422     * @see #setHeight(int)
4423     *
4424     * @attr ref android.R.styleable#TextView_maxHeight
4425     */
4426    public int getMaxHeight() {
4427        return mMaxMode == PIXELS ? mMaximum : -1;
4428    }
4429
4430    /**
4431     * Sets the height of the TextView to be exactly {@code lines} tall.
4432     * <p>
4433     * This value is used for height calculation if LayoutParams does not force TextView to have an
4434     * exact height. Setting this value overrides previous minimum/maximum height configurations
4435     * such as {@link #setMinLines(int)} or {@link #setMaxLines(int)}. {@link #setSingleLine()} will
4436     * set this value to 1.
4437     *
4438     * @param lines the exact height of the TextView in terms of lines
4439     *
4440     * @see #setHeight(int)
4441     *
4442     * @attr ref android.R.styleable#TextView_lines
4443     */
4444    @android.view.RemotableViewMethod
4445    public void setLines(int lines) {
4446        mMaximum = mMinimum = lines;
4447        mMaxMode = mMinMode = LINES;
4448
4449        requestLayout();
4450        invalidate();
4451    }
4452
4453    /**
4454     * Sets the height of the TextView to be exactly <code>pixels</code> tall.
4455     * <p>
4456     * This value is used for height calculation if LayoutParams does not force TextView to have an
4457     * exact height. Setting this value overrides previous minimum/maximum height configurations
4458     * such as {@link #setMinHeight(int)} or {@link #setMaxHeight(int)}.
4459     *
4460     * @param pixels the exact height of the TextView in terms of pixels
4461     *
4462     * @see #setLines(int)
4463     *
4464     * @attr ref android.R.styleable#TextView_height
4465     */
4466    @android.view.RemotableViewMethod
4467    public void setHeight(int pixels) {
4468        mMaximum = mMinimum = pixels;
4469        mMaxMode = mMinMode = PIXELS;
4470
4471        requestLayout();
4472        invalidate();
4473    }
4474
4475    /**
4476     * Sets the width of the TextView to be at least {@code minEms} wide.
4477     * <p>
4478     * This value is used for width calculation if LayoutParams does not force TextView to have an
4479     * exact width. Setting this value overrides previous minimum width configurations such as
4480     * {@link #setMinWidth(int)} or {@link #setWidth(int)}.
4481     *
4482     * @param minEms the minimum width of TextView in terms of ems
4483     *
4484     * @see #getMinEms()
4485     * @see #setEms(int)
4486     *
4487     * @attr ref android.R.styleable#TextView_minEms
4488     */
4489    @android.view.RemotableViewMethod
4490    public void setMinEms(int minEms) {
4491        mMinWidth = minEms;
4492        mMinWidthMode = EMS;
4493
4494        requestLayout();
4495        invalidate();
4496    }
4497
4498    /**
4499     * Returns the minimum width of TextView in terms of ems or -1 if the minimum width was set
4500     * using {@link #setMinWidth(int)} or {@link #setWidth(int)}.
4501     *
4502     * @return the minimum width of TextView in terms of ems. -1 if the minimum width is not
4503     *         defined in ems
4504     *
4505     * @see #setMinEms(int)
4506     * @see #setEms(int)
4507     *
4508     * @attr ref android.R.styleable#TextView_minEms
4509     */
4510    public int getMinEms() {
4511        return mMinWidthMode == EMS ? mMinWidth : -1;
4512    }
4513
4514    /**
4515     * Sets the width of the TextView to be at least {@code minPixels} wide.
4516     * <p>
4517     * This value is used for width calculation if LayoutParams does not force TextView to have an
4518     * exact width. Setting this value overrides previous minimum width configurations such as
4519     * {@link #setMinEms(int)} or {@link #setEms(int)}.
4520     * <p>
4521     * The value given here is different than {@link #setMinimumWidth(int)}. Between
4522     * {@code minWidth} and the value set in {@link #setMinimumWidth(int)}, the greater one is used
4523     * to decide the final width.
4524     *
4525     * @param minPixels the minimum width of TextView in terms of pixels
4526     *
4527     * @see #getMinWidth()
4528     * @see #setWidth(int)
4529     *
4530     * @attr ref android.R.styleable#TextView_minWidth
4531     */
4532    @android.view.RemotableViewMethod
4533    public void setMinWidth(int minPixels) {
4534        mMinWidth = minPixels;
4535        mMinWidthMode = PIXELS;
4536
4537        requestLayout();
4538        invalidate();
4539    }
4540
4541    /**
4542     * Returns the minimum width of TextView in terms of pixels or -1 if the minimum width was set
4543     * using {@link #setMinEms(int)} or {@link #setEms(int)}.
4544     *
4545     * @return the minimum width of TextView in terms of pixels or -1 if the minimum width is not
4546     *         defined in pixels
4547     *
4548     * @see #setMinWidth(int)
4549     * @see #setWidth(int)
4550     *
4551     * @attr ref android.R.styleable#TextView_minWidth
4552     */
4553    public int getMinWidth() {
4554        return mMinWidthMode == PIXELS ? mMinWidth : -1;
4555    }
4556
4557    /**
4558     * Sets the width of the TextView to be at most {@code maxEms} wide.
4559     * <p>
4560     * This value is used for width calculation if LayoutParams does not force TextView to have an
4561     * exact width. Setting this value overrides previous maximum width configurations such as
4562     * {@link #setMaxWidth(int)} or {@link #setWidth(int)}.
4563     *
4564     * @param maxEms the maximum width of TextView in terms of ems
4565     *
4566     * @see #getMaxEms()
4567     * @see #setEms(int)
4568     *
4569     * @attr ref android.R.styleable#TextView_maxEms
4570     */
4571    @android.view.RemotableViewMethod
4572    public void setMaxEms(int maxEms) {
4573        mMaxWidth = maxEms;
4574        mMaxWidthMode = EMS;
4575
4576        requestLayout();
4577        invalidate();
4578    }
4579
4580    /**
4581     * Returns the maximum width of TextView in terms of ems or -1 if the maximum width was set
4582     * using {@link #setMaxWidth(int)} or {@link #setWidth(int)}.
4583     *
4584     * @return the maximum width of TextView in terms of ems or -1 if the maximum width is not
4585     *         defined in ems
4586     *
4587     * @see #setMaxEms(int)
4588     * @see #setEms(int)
4589     *
4590     * @attr ref android.R.styleable#TextView_maxEms
4591     */
4592    public int getMaxEms() {
4593        return mMaxWidthMode == EMS ? mMaxWidth : -1;
4594    }
4595
4596    /**
4597     * Sets the width of the TextView to be at most {@code maxPixels} wide.
4598     * <p>
4599     * This value is used for width calculation if LayoutParams does not force TextView to have an
4600     * exact width. Setting this value overrides previous maximum width configurations such as
4601     * {@link #setMaxEms(int)} or {@link #setEms(int)}.
4602     *
4603     * @param maxPixels the maximum width of TextView in terms of pixels
4604     *
4605     * @see #getMaxWidth()
4606     * @see #setWidth(int)
4607     *
4608     * @attr ref android.R.styleable#TextView_maxWidth
4609     */
4610    @android.view.RemotableViewMethod
4611    public void setMaxWidth(int maxPixels) {
4612        mMaxWidth = maxPixels;
4613        mMaxWidthMode = PIXELS;
4614
4615        requestLayout();
4616        invalidate();
4617    }
4618
4619    /**
4620     * Returns the maximum width of TextView in terms of pixels or -1 if the maximum width was set
4621     * using {@link #setMaxEms(int)} or {@link #setEms(int)}.
4622     *
4623     * @return the maximum width of TextView in terms of pixels. -1 if the maximum width is not
4624     *         defined in pixels
4625     *
4626     * @see #setMaxWidth(int)
4627     * @see #setWidth(int)
4628     *
4629     * @attr ref android.R.styleable#TextView_maxWidth
4630     */
4631    public int getMaxWidth() {
4632        return mMaxWidthMode == PIXELS ? mMaxWidth : -1;
4633    }
4634
4635    /**
4636     * Sets the width of the TextView to be exactly {@code ems} wide.
4637     *
4638     * This value is used for width calculation if LayoutParams does not force TextView to have an
4639     * exact width. Setting this value overrides previous minimum/maximum configurations such as
4640     * {@link #setMinEms(int)} or {@link #setMaxEms(int)}.
4641     *
4642     * @param ems the exact width of the TextView in terms of ems
4643     *
4644     * @see #setWidth(int)
4645     *
4646     * @attr ref android.R.styleable#TextView_ems
4647     */
4648    @android.view.RemotableViewMethod
4649    public void setEms(int ems) {
4650        mMaxWidth = mMinWidth = ems;
4651        mMaxWidthMode = mMinWidthMode = EMS;
4652
4653        requestLayout();
4654        invalidate();
4655    }
4656
4657    /**
4658     * Sets the width of the TextView to be exactly {@code pixels} wide.
4659     * <p>
4660     * This value is used for width calculation if LayoutParams does not force TextView to have an
4661     * exact width. Setting this value overrides previous minimum/maximum width configurations
4662     * such as {@link #setMinWidth(int)} or {@link #setMaxWidth(int)}.
4663     *
4664     * @param pixels the exact width of the TextView in terms of pixels
4665     *
4666     * @see #setEms(int)
4667     *
4668     * @attr ref android.R.styleable#TextView_width
4669     */
4670    @android.view.RemotableViewMethod
4671    public void setWidth(int pixels) {
4672        mMaxWidth = mMinWidth = pixels;
4673        mMaxWidthMode = mMinWidthMode = PIXELS;
4674
4675        requestLayout();
4676        invalidate();
4677    }
4678
4679    /**
4680     * Sets line spacing for this TextView.  Each line will have its height
4681     * multiplied by <code>mult</code> and have <code>add</code> added to it.
4682     *
4683     * @attr ref android.R.styleable#TextView_lineSpacingExtra
4684     * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
4685     */
4686    public void setLineSpacing(float add, float mult) {
4687        if (mSpacingAdd != add || mSpacingMult != mult) {
4688            mSpacingAdd = add;
4689            mSpacingMult = mult;
4690
4691            if (mLayout != null) {
4692                nullLayouts();
4693                requestLayout();
4694                invalidate();
4695            }
4696        }
4697    }
4698
4699    /**
4700     * Gets the line spacing multiplier
4701     *
4702     * @return the value by which each line's height is multiplied to get its actual height.
4703     *
4704     * @see #setLineSpacing(float, float)
4705     * @see #getLineSpacingExtra()
4706     *
4707     * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
4708     */
4709    public float getLineSpacingMultiplier() {
4710        return mSpacingMult;
4711    }
4712
4713    /**
4714     * Gets the line spacing extra space
4715     *
4716     * @return the extra space that is added to the height of each lines of this TextView.
4717     *
4718     * @see #setLineSpacing(float, float)
4719     * @see #getLineSpacingMultiplier()
4720     *
4721     * @attr ref android.R.styleable#TextView_lineSpacingExtra
4722     */
4723    public float getLineSpacingExtra() {
4724        return mSpacingAdd;
4725    }
4726
4727    /**
4728     * Convenience method to append the specified text to the TextView's
4729     * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
4730     * if it was not already editable.
4731     *
4732     * @param text text to be appended to the already displayed text
4733     */
4734    public final void append(CharSequence text) {
4735        append(text, 0, text.length());
4736    }
4737
4738    /**
4739     * Convenience method to append the specified text slice to the TextView's
4740     * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
4741     * if it was not already editable.
4742     *
4743     * @param text text to be appended to the already displayed text
4744     * @param start the index of the first character in the {@code text}
4745     * @param end the index of the character following the last character in the {@code text}
4746     *
4747     * @see Appendable#append(CharSequence, int, int)
4748     */
4749    public void append(CharSequence text, int start, int end) {
4750        if (!(mText instanceof Editable)) {
4751            setText(mText, BufferType.EDITABLE);
4752        }
4753
4754        ((Editable) mText).append(text, start, end);
4755
4756        if (mAutoLinkMask != 0) {
4757            boolean linksWereAdded = Linkify.addLinks((Spannable) mText, mAutoLinkMask);
4758            // Do not change the movement method for text that support text selection as it
4759            // would prevent an arbitrary cursor displacement.
4760            if (linksWereAdded && mLinksClickable && !textCanBeSelected()) {
4761                setMovementMethod(LinkMovementMethod.getInstance());
4762            }
4763        }
4764    }
4765
4766    private void updateTextColors() {
4767        boolean inval = false;
4768        int color = mTextColor.getColorForState(getDrawableState(), 0);
4769        if (color != mCurTextColor) {
4770            mCurTextColor = color;
4771            inval = true;
4772        }
4773        if (mLinkTextColor != null) {
4774            color = mLinkTextColor.getColorForState(getDrawableState(), 0);
4775            if (color != mTextPaint.linkColor) {
4776                mTextPaint.linkColor = color;
4777                inval = true;
4778            }
4779        }
4780        if (mHintTextColor != null) {
4781            color = mHintTextColor.getColorForState(getDrawableState(), 0);
4782            if (color != mCurHintTextColor) {
4783                mCurHintTextColor = color;
4784                if (mText.length() == 0) {
4785                    inval = true;
4786                }
4787            }
4788        }
4789        if (inval) {
4790            // Text needs to be redrawn with the new color
4791            if (mEditor != null) mEditor.invalidateTextDisplayList();
4792            invalidate();
4793        }
4794    }
4795
4796    @Override
4797    protected void drawableStateChanged() {
4798        super.drawableStateChanged();
4799
4800        if (mTextColor != null && mTextColor.isStateful()
4801                || (mHintTextColor != null && mHintTextColor.isStateful())
4802                || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
4803            updateTextColors();
4804        }
4805
4806        if (mDrawables != null) {
4807            final int[] state = getDrawableState();
4808            for (Drawable dr : mDrawables.mShowing) {
4809                if (dr != null && dr.isStateful() && dr.setState(state)) {
4810                    invalidateDrawable(dr);
4811                }
4812            }
4813        }
4814    }
4815
4816    @Override
4817    public void drawableHotspotChanged(float x, float y) {
4818        super.drawableHotspotChanged(x, y);
4819
4820        if (mDrawables != null) {
4821            for (Drawable dr : mDrawables.mShowing) {
4822                if (dr != null) {
4823                    dr.setHotspot(x, y);
4824                }
4825            }
4826        }
4827    }
4828
4829    @Override
4830    public Parcelable onSaveInstanceState() {
4831        Parcelable superState = super.onSaveInstanceState();
4832
4833        // Save state if we are forced to
4834        final boolean freezesText = getFreezesText();
4835        boolean hasSelection = false;
4836        int start = -1;
4837        int end = -1;
4838
4839        if (mText != null) {
4840            start = getSelectionStart();
4841            end = getSelectionEnd();
4842            if (start >= 0 || end >= 0) {
4843                // Or save state if there is a selection
4844                hasSelection = true;
4845            }
4846        }
4847
4848        if (freezesText || hasSelection) {
4849            SavedState ss = new SavedState(superState);
4850
4851            if (freezesText) {
4852                if (mText instanceof Spanned) {
4853                    final Spannable sp = new SpannableStringBuilder(mText);
4854
4855                    if (mEditor != null) {
4856                        removeMisspelledSpans(sp);
4857                        sp.removeSpan(mEditor.mSuggestionRangeSpan);
4858                    }
4859
4860                    ss.text = sp;
4861                } else {
4862                    ss.text = mText.toString();
4863                }
4864            }
4865
4866            if (hasSelection) {
4867                // XXX Should also save the current scroll position!
4868                ss.selStart = start;
4869                ss.selEnd = end;
4870            }
4871
4872            if (isFocused() && start >= 0 && end >= 0) {
4873                ss.frozenWithFocus = true;
4874            }
4875
4876            ss.error = getError();
4877
4878            if (mEditor != null) {
4879                ss.editorState = mEditor.saveInstanceState();
4880            }
4881            return ss;
4882        }
4883
4884        return superState;
4885    }
4886
4887    void removeMisspelledSpans(Spannable spannable) {
4888        SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(),
4889                SuggestionSpan.class);
4890        for (int i = 0; i < suggestionSpans.length; i++) {
4891            int flags = suggestionSpans[i].getFlags();
4892            if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
4893                    && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
4894                spannable.removeSpan(suggestionSpans[i]);
4895            }
4896        }
4897    }
4898
4899    @Override
4900    public void onRestoreInstanceState(Parcelable state) {
4901        if (!(state instanceof SavedState)) {
4902            super.onRestoreInstanceState(state);
4903            return;
4904        }
4905
4906        SavedState ss = (SavedState) state;
4907        super.onRestoreInstanceState(ss.getSuperState());
4908
4909        // XXX restore buffer type too, as well as lots of other stuff
4910        if (ss.text != null) {
4911            setText(ss.text);
4912        }
4913
4914        if (ss.selStart >= 0 && ss.selEnd >= 0) {
4915            if (mText instanceof Spannable) {
4916                int len = mText.length();
4917
4918                if (ss.selStart > len || ss.selEnd > len) {
4919                    String restored = "";
4920
4921                    if (ss.text != null) {
4922                        restored = "(restored) ";
4923                    }
4924
4925                    Log.e(LOG_TAG, "Saved cursor position " + ss.selStart + "/" + ss.selEnd
4926                            + " out of range for " + restored + "text " + mText);
4927                } else {
4928                    Selection.setSelection((Spannable) mText, ss.selStart, ss.selEnd);
4929
4930                    if (ss.frozenWithFocus) {
4931                        createEditorIfNeeded();
4932                        mEditor.mFrozenWithFocus = true;
4933                    }
4934                }
4935            }
4936        }
4937
4938        if (ss.error != null) {
4939            final CharSequence error = ss.error;
4940            // Display the error later, after the first layout pass
4941            post(new Runnable() {
4942                public void run() {
4943                    if (mEditor == null || !mEditor.mErrorWasChanged) {
4944                        setError(error);
4945                    }
4946                }
4947            });
4948        }
4949
4950        if (ss.editorState != null) {
4951            createEditorIfNeeded();
4952            mEditor.restoreInstanceState(ss.editorState);
4953        }
4954    }
4955
4956    /**
4957     * Control whether this text view saves its entire text contents when
4958     * freezing to an icicle, in addition to dynamic state such as cursor
4959     * position.  By default this is false, not saving the text.  Set to true
4960     * if the text in the text view is not being saved somewhere else in
4961     * persistent storage (such as in a content provider) so that if the
4962     * view is later thawed the user will not lose their data. For
4963     * {@link android.widget.EditText} it is always enabled, regardless of
4964     * the value of the attribute.
4965     *
4966     * @param freezesText Controls whether a frozen icicle should include the
4967     * entire text data: true to include it, false to not.
4968     *
4969     * @attr ref android.R.styleable#TextView_freezesText
4970     */
4971    @android.view.RemotableViewMethod
4972    public void setFreezesText(boolean freezesText) {
4973        mFreezesText = freezesText;
4974    }
4975
4976    /**
4977     * Return whether this text view is including its entire text contents
4978     * in frozen icicles. For {@link android.widget.EditText} it always returns true.
4979     *
4980     * @return Returns true if text is included, false if it isn't.
4981     *
4982     * @see #setFreezesText
4983     */
4984    public boolean getFreezesText() {
4985        return mFreezesText;
4986    }
4987
4988    ///////////////////////////////////////////////////////////////////////////
4989
4990    /**
4991     * Sets the Factory used to create new {@link Editable Editables}.
4992     *
4993     * @param factory {@link android.text.Editable.Factory Editable.Factory} to be used
4994     *
4995     * @see android.text.Editable.Factory
4996     * @see android.widget.TextView.BufferType#EDITABLE
4997     */
4998    public final void setEditableFactory(Editable.Factory factory) {
4999        mEditableFactory = factory;
5000        setText(mText);
5001    }
5002
5003    /**
5004     * Sets the Factory used to create new {@link Spannable Spannables}.
5005     *
5006     * @param factory {@link android.text.Spannable.Factory Spannable.Factory} to be used
5007     *
5008     * @see android.text.Spannable.Factory
5009     * @see android.widget.TextView.BufferType#SPANNABLE
5010     */
5011    public final void setSpannableFactory(Spannable.Factory factory) {
5012        mSpannableFactory = factory;
5013        setText(mText);
5014    }
5015
5016    /**
5017     * Sets the text to be displayed. TextView <em>does not</em> accept
5018     * HTML-like formatting, which you can do with text strings in XML resource files.
5019     * To style your strings, attach android.text.style.* objects to a
5020     * {@link android.text.SpannableString}, or see the
5021     * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
5022     * Available Resource Types</a> documentation for an example of setting
5023     * formatted text in the XML resource file.
5024     * <p/>
5025     * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
5026     * intermediate {@link Spannable Spannables}. Likewise it will use
5027     * {@link android.text.Editable.Factory} to create final or intermediate
5028     * {@link Editable Editables}.
5029     *
5030     * @param text text to be displayed
5031     *
5032     * @attr ref android.R.styleable#TextView_text
5033     */
5034    @android.view.RemotableViewMethod
5035    public final void setText(CharSequence text) {
5036        setText(text, mBufferType);
5037    }
5038
5039    /**
5040     * Sets the text to be displayed but retains the cursor position. Same as
5041     * {@link #setText(CharSequence)} except that the cursor position (if any) is retained in the
5042     * new text.
5043     * <p/>
5044     * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
5045     * intermediate {@link Spannable Spannables}. Likewise it will use
5046     * {@link android.text.Editable.Factory} to create final or intermediate
5047     * {@link Editable Editables}.
5048     *
5049     * @param text text to be displayed
5050     *
5051     * @see #setText(CharSequence)
5052     */
5053    @android.view.RemotableViewMethod
5054    public final void setTextKeepState(CharSequence text) {
5055        setTextKeepState(text, mBufferType);
5056    }
5057
5058    /**
5059     * Sets the text to be displayed and the {@link android.widget.TextView.BufferType}.
5060     * <p/>
5061     * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
5062     * intermediate {@link Spannable Spannables}. Likewise it will use
5063     * {@link android.text.Editable.Factory} to create final or intermediate
5064     * {@link Editable Editables}.
5065     *
5066     * @param text text to be displayed
5067     * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
5068     *              stored as a static text, styleable/spannable text, or editable text
5069     *
5070     * @see #setText(CharSequence)
5071     * @see android.widget.TextView.BufferType
5072     * @see #setSpannableFactory(Spannable.Factory)
5073     * @see #setEditableFactory(Editable.Factory)
5074     *
5075     * @attr ref android.R.styleable#TextView_text
5076     * @attr ref android.R.styleable#TextView_bufferType
5077     */
5078    public void setText(CharSequence text, BufferType type) {
5079        setText(text, type, true, 0);
5080
5081        if (mCharWrapper != null) {
5082            mCharWrapper.mChars = null;
5083        }
5084    }
5085
5086    private void setText(CharSequence text, BufferType type,
5087                         boolean notifyBefore, int oldlen) {
5088        mTextFromResource = false;
5089        if (text == null) {
5090            text = "";
5091        }
5092
5093        // If suggestions are not enabled, remove the suggestion spans from the text
5094        if (!isSuggestionsEnabled()) {
5095            text = removeSuggestionSpans(text);
5096        }
5097
5098        if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
5099
5100        if (text instanceof Spanned
5101                && ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
5102            if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {
5103                setHorizontalFadingEdgeEnabled(true);
5104                mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
5105            } else {
5106                setHorizontalFadingEdgeEnabled(false);
5107                mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
5108            }
5109            setEllipsize(TextUtils.TruncateAt.MARQUEE);
5110        }
5111
5112        int n = mFilters.length;
5113        for (int i = 0; i < n; i++) {
5114            CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);
5115            if (out != null) {
5116                text = out;
5117            }
5118        }
5119
5120        if (notifyBefore) {
5121            if (mText != null) {
5122                oldlen = mText.length();
5123                sendBeforeTextChanged(mText, 0, oldlen, text.length());
5124            } else {
5125                sendBeforeTextChanged("", 0, 0, text.length());
5126            }
5127        }
5128
5129        boolean needEditableForNotification = false;
5130
5131        if (mListeners != null && mListeners.size() != 0) {
5132            needEditableForNotification = true;
5133        }
5134
5135        if (type == BufferType.EDITABLE || getKeyListener() != null
5136                || needEditableForNotification) {
5137            createEditorIfNeeded();
5138            mEditor.forgetUndoRedo();
5139            Editable t = mEditableFactory.newEditable(text);
5140            text = t;
5141            setFilters(t, mFilters);
5142            InputMethodManager imm = InputMethodManager.peekInstance();
5143            if (imm != null) imm.restartInput(this);
5144        } else if (type == BufferType.SPANNABLE || mMovement != null) {
5145            text = mSpannableFactory.newSpannable(text);
5146        } else if (!(text instanceof CharWrapper)) {
5147            text = TextUtils.stringOrSpannedString(text);
5148        }
5149
5150        if (mAutoLinkMask != 0) {
5151            Spannable s2;
5152
5153            if (type == BufferType.EDITABLE || text instanceof Spannable) {
5154                s2 = (Spannable) text;
5155            } else {
5156                s2 = mSpannableFactory.newSpannable(text);
5157            }
5158
5159            if (Linkify.addLinks(s2, mAutoLinkMask)) {
5160                text = s2;
5161                type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
5162
5163                /*
5164                 * We must go ahead and set the text before changing the
5165                 * movement method, because setMovementMethod() may call
5166                 * setText() again to try to upgrade the buffer type.
5167                 */
5168                mText = text;
5169
5170                // Do not change the movement method for text that support text selection as it
5171                // would prevent an arbitrary cursor displacement.
5172                if (mLinksClickable && !textCanBeSelected()) {
5173                    setMovementMethod(LinkMovementMethod.getInstance());
5174                }
5175            }
5176        }
5177
5178        mBufferType = type;
5179        mText = text;
5180
5181        if (mTransformation == null) {
5182            mTransformed = text;
5183        } else {
5184            mTransformed = mTransformation.getTransformation(text, this);
5185        }
5186
5187        final int textLength = text.length();
5188
5189        if (text instanceof Spannable && !mAllowTransformationLengthChange) {
5190            Spannable sp = (Spannable) text;
5191
5192            // Remove any ChangeWatchers that might have come from other TextViews.
5193            final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
5194            final int count = watchers.length;
5195            for (int i = 0; i < count; i++) {
5196                sp.removeSpan(watchers[i]);
5197            }
5198
5199            if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();
5200
5201            sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE
5202                    | (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
5203
5204            if (mEditor != null) mEditor.addSpanWatchers(sp);
5205
5206            if (mTransformation != null) {
5207                sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
5208            }
5209
5210            if (mMovement != null) {
5211                mMovement.initialize(this, (Spannable) text);
5212
5213                /*
5214                 * Initializing the movement method will have set the
5215                 * selection, so reset mSelectionMoved to keep that from
5216                 * interfering with the normal on-focus selection-setting.
5217                 */
5218                if (mEditor != null) mEditor.mSelectionMoved = false;
5219            }
5220        }
5221
5222        if (mLayout != null) {
5223            checkForRelayout();
5224        }
5225
5226        sendOnTextChanged(text, 0, oldlen, textLength);
5227        onTextChanged(text, 0, oldlen, textLength);
5228
5229        notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
5230
5231        if (needEditableForNotification) {
5232            sendAfterTextChanged((Editable) text);
5233        } else {
5234            // Always notify AutoFillManager - it will return right away if autofill is disabled.
5235            notifyAutoFillManagerAfterTextChanged();
5236        }
5237
5238        // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
5239        if (mEditor != null) mEditor.prepareCursorControllers();
5240    }
5241
5242    /**
5243     * Sets the TextView to display the specified slice of the specified
5244     * char array. You must promise that you will not change the contents
5245     * of the array except for right before another call to setText(),
5246     * since the TextView has no way to know that the text
5247     * has changed and that it needs to invalidate and re-layout.
5248     *
5249     * @param text char array to be displayed
5250     * @param start start index in the char array
5251     * @param len length of char count after {@code start}
5252     */
5253    public final void setText(char[] text, int start, int len) {
5254        int oldlen = 0;
5255
5256        if (start < 0 || len < 0 || start + len > text.length) {
5257            throw new IndexOutOfBoundsException(start + ", " + len);
5258        }
5259
5260        /*
5261         * We must do the before-notification here ourselves because if
5262         * the old text is a CharWrapper we destroy it before calling
5263         * into the normal path.
5264         */
5265        if (mText != null) {
5266            oldlen = mText.length();
5267            sendBeforeTextChanged(mText, 0, oldlen, len);
5268        } else {
5269            sendBeforeTextChanged("", 0, 0, len);
5270        }
5271
5272        if (mCharWrapper == null) {
5273            mCharWrapper = new CharWrapper(text, start, len);
5274        } else {
5275            mCharWrapper.set(text, start, len);
5276        }
5277
5278        setText(mCharWrapper, mBufferType, false, oldlen);
5279    }
5280
5281    /**
5282     * Sets the text to be displayed and the {@link android.widget.TextView.BufferType} but retains
5283     * the cursor position. Same as
5284     * {@link #setText(CharSequence, android.widget.TextView.BufferType)} except that the cursor
5285     * position (if any) is retained in the new text.
5286     * <p/>
5287     * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
5288     * intermediate {@link Spannable Spannables}. Likewise it will use
5289     * {@link android.text.Editable.Factory} to create final or intermediate
5290     * {@link Editable Editables}.
5291     *
5292     * @param text text to be displayed
5293     * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
5294     *              stored as a static text, styleable/spannable text, or editable text
5295     *
5296     * @see #setText(CharSequence, android.widget.TextView.BufferType)
5297     */
5298    public final void setTextKeepState(CharSequence text, BufferType type) {
5299        int start = getSelectionStart();
5300        int end = getSelectionEnd();
5301        int len = text.length();
5302
5303        setText(text, type);
5304
5305        if (start >= 0 || end >= 0) {
5306            if (mText instanceof Spannable) {
5307                Selection.setSelection((Spannable) mText,
5308                                       Math.max(0, Math.min(start, len)),
5309                                       Math.max(0, Math.min(end, len)));
5310            }
5311        }
5312    }
5313
5314    /**
5315     * Sets the text to be displayed using a string resource identifier.
5316     *
5317     * @param resid the resource identifier of the string resource to be displayed
5318     *
5319     * @see #setText(CharSequence)
5320     *
5321     * @attr ref android.R.styleable#TextView_text
5322     */
5323    @android.view.RemotableViewMethod
5324    public final void setText(@StringRes int resid) {
5325        setText(getContext().getResources().getText(resid));
5326        mTextFromResource = true;
5327    }
5328
5329    /**
5330     * Sets the text to be displayed using a string resource identifier and the
5331     * {@link android.widget.TextView.BufferType}.
5332     * <p/>
5333     * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
5334     * intermediate {@link Spannable Spannables}. Likewise it will use
5335     * {@link android.text.Editable.Factory} to create final or intermediate
5336     * {@link Editable Editables}.
5337     *
5338     * @param resid the resource identifier of the string resource to be displayed
5339     * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
5340     *              stored as a static text, styleable/spannable text, or editable text
5341     *
5342     * @see #setText(int)
5343     * @see #setText(CharSequence)
5344     * @see android.widget.TextView.BufferType
5345     * @see #setSpannableFactory(Spannable.Factory)
5346     * @see #setEditableFactory(Editable.Factory)
5347     *
5348     * @attr ref android.R.styleable#TextView_text
5349     * @attr ref android.R.styleable#TextView_bufferType
5350     */
5351    public final void setText(@StringRes int resid, BufferType type) {
5352        setText(getContext().getResources().getText(resid), type);
5353        mTextFromResource = true;
5354    }
5355
5356    /**
5357     * Sets the text to be displayed when the text of the TextView is empty.
5358     * Null means to use the normal empty text. The hint does not currently
5359     * participate in determining the size of the view.
5360     *
5361     * @attr ref android.R.styleable#TextView_hint
5362     */
5363    @android.view.RemotableViewMethod
5364    public final void setHint(CharSequence hint) {
5365        mHint = TextUtils.stringOrSpannedString(hint);
5366
5367        if (mLayout != null) {
5368            checkForRelayout();
5369        }
5370
5371        if (mText.length() == 0) {
5372            invalidate();
5373        }
5374
5375        // Invalidate display list if hint is currently used
5376        if (mEditor != null && mText.length() == 0 && mHint != null) {
5377            mEditor.invalidateTextDisplayList();
5378        }
5379    }
5380
5381    /**
5382     * Sets the text to be displayed when the text of the TextView is empty,
5383     * from a resource.
5384     *
5385     * @attr ref android.R.styleable#TextView_hint
5386     */
5387    @android.view.RemotableViewMethod
5388    public final void setHint(@StringRes int resid) {
5389        setHint(getContext().getResources().getText(resid));
5390    }
5391
5392    /**
5393     * Returns the hint that is displayed when the text of the TextView
5394     * is empty.
5395     *
5396     * @attr ref android.R.styleable#TextView_hint
5397     */
5398    @ViewDebug.CapturedViewProperty
5399    public CharSequence getHint() {
5400        return mHint;
5401    }
5402
5403    boolean isSingleLine() {
5404        return mSingleLine;
5405    }
5406
5407    private static boolean isMultilineInputType(int type) {
5408        return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE))
5409                == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
5410    }
5411
5412    /**
5413     * Removes the suggestion spans.
5414     */
5415    CharSequence removeSuggestionSpans(CharSequence text) {
5416        if (text instanceof Spanned) {
5417            Spannable spannable;
5418            if (text instanceof Spannable) {
5419                spannable = (Spannable) text;
5420            } else {
5421                spannable = mSpannableFactory.newSpannable(text);
5422                text = spannable;
5423            }
5424
5425            SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
5426            for (int i = 0; i < spans.length; i++) {
5427                spannable.removeSpan(spans[i]);
5428            }
5429        }
5430        return text;
5431    }
5432
5433    /**
5434     * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This
5435     * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)},
5436     * to match the given content type.  If the given content type is {@link EditorInfo#TYPE_NULL}
5437     * then a soft keyboard will not be displayed for this text view.
5438     *
5439     * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be
5440     * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input
5441     * type.
5442     *
5443     * @see #getInputType()
5444     * @see #setRawInputType(int)
5445     * @see android.text.InputType
5446     * @attr ref android.R.styleable#TextView_inputType
5447     */
5448    public void setInputType(int type) {
5449        final boolean wasPassword = isPasswordInputType(getInputType());
5450        final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType());
5451        setInputType(type, false);
5452        final boolean isPassword = isPasswordInputType(type);
5453        final boolean isVisiblePassword = isVisiblePasswordInputType(type);
5454        boolean forceUpdate = false;
5455        if (isPassword) {
5456            setTransformationMethod(PasswordTransformationMethod.getInstance());
5457            setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE, 0);
5458        } else if (isVisiblePassword) {
5459            if (mTransformation == PasswordTransformationMethod.getInstance()) {
5460                forceUpdate = true;
5461            }
5462            setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE, 0);
5463        } else if (wasPassword || wasVisiblePassword) {
5464            // not in password mode, clean up typeface and transformation
5465            setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, -1, -1);
5466            if (mTransformation == PasswordTransformationMethod.getInstance()) {
5467                forceUpdate = true;
5468            }
5469        }
5470
5471        boolean singleLine = !isMultilineInputType(type);
5472
5473        // We need to update the single line mode if it has changed or we
5474        // were previously in password mode.
5475        if (mSingleLine != singleLine || forceUpdate) {
5476            // Change single line mode, but only change the transformation if
5477            // we are not in password mode.
5478            applySingleLine(singleLine, !isPassword, true);
5479        }
5480
5481        if (!isSuggestionsEnabled()) {
5482            mText = removeSuggestionSpans(mText);
5483        }
5484
5485        InputMethodManager imm = InputMethodManager.peekInstance();
5486        if (imm != null) imm.restartInput(this);
5487    }
5488
5489    /**
5490     * It would be better to rely on the input type for everything. A password inputType should have
5491     * a password transformation. We should hence use isPasswordInputType instead of this method.
5492     *
5493     * We should:
5494     * - Call setInputType in setKeyListener instead of changing the input type directly (which
5495     * would install the correct transformation).
5496     * - Refuse the installation of a non-password transformation in setTransformation if the input
5497     * type is password.
5498     *
5499     * However, this is like this for legacy reasons and we cannot break existing apps. This method
5500     * is useful since it matches what the user can see (obfuscated text or not).
5501     *
5502     * @return true if the current transformation method is of the password type.
5503     */
5504    boolean hasPasswordTransformationMethod() {
5505        return mTransformation instanceof PasswordTransformationMethod;
5506    }
5507
5508    private static boolean isPasswordInputType(int inputType) {
5509        final int variation =
5510                inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
5511        return variation
5512                == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)
5513                || variation
5514                == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD)
5515                || variation
5516                == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
5517    }
5518
5519    private static boolean isVisiblePasswordInputType(int inputType) {
5520        final int variation =
5521                inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
5522        return variation
5523                == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
5524    }
5525
5526    /**
5527     * Directly change the content type integer of the text view, without
5528     * modifying any other state.
5529     * @see #setInputType(int)
5530     * @see android.text.InputType
5531     * @attr ref android.R.styleable#TextView_inputType
5532     */
5533    public void setRawInputType(int type) {
5534        if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
5535        createEditorIfNeeded();
5536        mEditor.mInputType = type;
5537    }
5538
5539    private void setInputType(int type, boolean direct) {
5540        final int cls = type & EditorInfo.TYPE_MASK_CLASS;
5541        KeyListener input;
5542        if (cls == EditorInfo.TYPE_CLASS_TEXT) {
5543            boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
5544            TextKeyListener.Capitalize cap;
5545            if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
5546                cap = TextKeyListener.Capitalize.CHARACTERS;
5547            } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
5548                cap = TextKeyListener.Capitalize.WORDS;
5549            } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
5550                cap = TextKeyListener.Capitalize.SENTENCES;
5551            } else {
5552                cap = TextKeyListener.Capitalize.NONE;
5553            }
5554            input = TextKeyListener.getInstance(autotext, cap);
5555        } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
5556            input = DigitsKeyListener.getInstance(
5557                    (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
5558                    (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
5559        } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
5560            switch (type & EditorInfo.TYPE_MASK_VARIATION) {
5561                case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
5562                    input = DateKeyListener.getInstance();
5563                    break;
5564                case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
5565                    input = TimeKeyListener.getInstance();
5566                    break;
5567                default:
5568                    input = DateTimeKeyListener.getInstance();
5569                    break;
5570            }
5571        } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
5572            input = DialerKeyListener.getInstance();
5573        } else {
5574            input = TextKeyListener.getInstance();
5575        }
5576        setRawInputType(type);
5577        if (direct) {
5578            createEditorIfNeeded();
5579            mEditor.mKeyListener = input;
5580        } else {
5581            setKeyListenerOnly(input);
5582        }
5583    }
5584
5585    /**
5586     * Get the type of the editable content.
5587     *
5588     * @see #setInputType(int)
5589     * @see android.text.InputType
5590     */
5591    public int getInputType() {
5592        return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType;
5593    }
5594
5595    /**
5596     * Change the editor type integer associated with the text view, which
5597     * will be reported to an IME with {@link EditorInfo#imeOptions} when it
5598     * has focus.
5599     * @see #getImeOptions
5600     * @see android.view.inputmethod.EditorInfo
5601     * @attr ref android.R.styleable#TextView_imeOptions
5602     */
5603    public void setImeOptions(int imeOptions) {
5604        createEditorIfNeeded();
5605        mEditor.createInputContentTypeIfNeeded();
5606        mEditor.mInputContentType.imeOptions = imeOptions;
5607    }
5608
5609    /**
5610     * Get the type of the IME editor.
5611     *
5612     * @see #setImeOptions(int)
5613     * @see android.view.inputmethod.EditorInfo
5614     */
5615    public int getImeOptions() {
5616        return mEditor != null && mEditor.mInputContentType != null
5617                ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL;
5618    }
5619
5620    /**
5621     * Change the custom IME action associated with the text view, which
5622     * will be reported to an IME with {@link EditorInfo#actionLabel}
5623     * and {@link EditorInfo#actionId} when it has focus.
5624     * @see #getImeActionLabel
5625     * @see #getImeActionId
5626     * @see android.view.inputmethod.EditorInfo
5627     * @attr ref android.R.styleable#TextView_imeActionLabel
5628     * @attr ref android.R.styleable#TextView_imeActionId
5629     */
5630    public void setImeActionLabel(CharSequence label, int actionId) {
5631        createEditorIfNeeded();
5632        mEditor.createInputContentTypeIfNeeded();
5633        mEditor.mInputContentType.imeActionLabel = label;
5634        mEditor.mInputContentType.imeActionId = actionId;
5635    }
5636
5637    /**
5638     * Get the IME action label previous set with {@link #setImeActionLabel}.
5639     *
5640     * @see #setImeActionLabel
5641     * @see android.view.inputmethod.EditorInfo
5642     */
5643    public CharSequence getImeActionLabel() {
5644        return mEditor != null && mEditor.mInputContentType != null
5645                ? mEditor.mInputContentType.imeActionLabel : null;
5646    }
5647
5648    /**
5649     * Get the IME action ID previous set with {@link #setImeActionLabel}.
5650     *
5651     * @see #setImeActionLabel
5652     * @see android.view.inputmethod.EditorInfo
5653     */
5654    public int getImeActionId() {
5655        return mEditor != null && mEditor.mInputContentType != null
5656                ? mEditor.mInputContentType.imeActionId : 0;
5657    }
5658
5659    /**
5660     * Set a special listener to be called when an action is performed
5661     * on the text view.  This will be called when the enter key is pressed,
5662     * or when an action supplied to the IME is selected by the user.  Setting
5663     * this means that the normal hard key event will not insert a newline
5664     * into the text view, even if it is multi-line; holding down the ALT
5665     * modifier will, however, allow the user to insert a newline character.
5666     */
5667    public void setOnEditorActionListener(OnEditorActionListener l) {
5668        createEditorIfNeeded();
5669        mEditor.createInputContentTypeIfNeeded();
5670        mEditor.mInputContentType.onEditorActionListener = l;
5671    }
5672
5673    /**
5674     * Called when an attached input method calls
5675     * {@link InputConnection#performEditorAction(int)
5676     * InputConnection.performEditorAction()}
5677     * for this text view.  The default implementation will call your action
5678     * listener supplied to {@link #setOnEditorActionListener}, or perform
5679     * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
5680     * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS
5681     * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE
5682     * EditorInfo.IME_ACTION_DONE}.
5683     *
5684     * <p>For backwards compatibility, if no IME options have been set and the
5685     * text view would not normally advance focus on enter, then
5686     * the NEXT and DONE actions received here will be turned into an enter
5687     * key down/up pair to go through the normal key handling.
5688     *
5689     * @param actionCode The code of the action being performed.
5690     *
5691     * @see #setOnEditorActionListener
5692     */
5693    public void onEditorAction(int actionCode) {
5694        final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType;
5695        if (ict != null) {
5696            if (ict.onEditorActionListener != null) {
5697                if (ict.onEditorActionListener.onEditorAction(this,
5698                        actionCode, null)) {
5699                    return;
5700                }
5701            }
5702
5703            // This is the handling for some default action.
5704            // Note that for backwards compatibility we don't do this
5705            // default handling if explicit ime options have not been given,
5706            // instead turning this into the normal enter key codes that an
5707            // app may be expecting.
5708            if (actionCode == EditorInfo.IME_ACTION_NEXT) {
5709                View v = focusSearch(FOCUS_FORWARD);
5710                if (v != null) {
5711                    if (!v.requestFocus(FOCUS_FORWARD)) {
5712                        throw new IllegalStateException("focus search returned a view "
5713                                + "that wasn't able to take focus!");
5714                    }
5715                }
5716                return;
5717
5718            } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
5719                View v = focusSearch(FOCUS_BACKWARD);
5720                if (v != null) {
5721                    if (!v.requestFocus(FOCUS_BACKWARD)) {
5722                        throw new IllegalStateException("focus search returned a view "
5723                                + "that wasn't able to take focus!");
5724                    }
5725                }
5726                return;
5727
5728            } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
5729                InputMethodManager imm = InputMethodManager.peekInstance();
5730                if (imm != null && imm.isActive(this)) {
5731                    imm.hideSoftInputFromWindow(getWindowToken(), 0);
5732                }
5733                return;
5734            }
5735        }
5736
5737        ViewRootImpl viewRootImpl = getViewRootImpl();
5738        if (viewRootImpl != null) {
5739            long eventTime = SystemClock.uptimeMillis();
5740            viewRootImpl.dispatchKeyFromIme(
5741                    new KeyEvent(eventTime, eventTime,
5742                    KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
5743                    KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
5744                    KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
5745                    | KeyEvent.FLAG_EDITOR_ACTION));
5746            viewRootImpl.dispatchKeyFromIme(
5747                    new KeyEvent(SystemClock.uptimeMillis(), eventTime,
5748                    KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
5749                    KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
5750                    KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
5751                    | KeyEvent.FLAG_EDITOR_ACTION));
5752        }
5753    }
5754
5755    /**
5756     * Set the private content type of the text, which is the
5757     * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
5758     * field that will be filled in when creating an input connection.
5759     *
5760     * @see #getPrivateImeOptions()
5761     * @see EditorInfo#privateImeOptions
5762     * @attr ref android.R.styleable#TextView_privateImeOptions
5763     */
5764    public void setPrivateImeOptions(String type) {
5765        createEditorIfNeeded();
5766        mEditor.createInputContentTypeIfNeeded();
5767        mEditor.mInputContentType.privateImeOptions = type;
5768    }
5769
5770    /**
5771     * Get the private type of the content.
5772     *
5773     * @see #setPrivateImeOptions(String)
5774     * @see EditorInfo#privateImeOptions
5775     */
5776    public String getPrivateImeOptions() {
5777        return mEditor != null && mEditor.mInputContentType != null
5778                ? mEditor.mInputContentType.privateImeOptions : null;
5779    }
5780
5781    /**
5782     * Set the extra input data of the text, which is the
5783     * {@link EditorInfo#extras TextBoxAttribute.extras}
5784     * Bundle that will be filled in when creating an input connection.  The
5785     * given integer is the resource identifier of an XML resource holding an
5786     * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
5787     *
5788     * @see #getInputExtras(boolean)
5789     * @see EditorInfo#extras
5790     * @attr ref android.R.styleable#TextView_editorExtras
5791     */
5792    public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException {
5793        createEditorIfNeeded();
5794        XmlResourceParser parser = getResources().getXml(xmlResId);
5795        mEditor.createInputContentTypeIfNeeded();
5796        mEditor.mInputContentType.extras = new Bundle();
5797        getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras);
5798    }
5799
5800    /**
5801     * Retrieve the input extras currently associated with the text view, which
5802     * can be viewed as well as modified.
5803     *
5804     * @param create If true, the extras will be created if they don't already
5805     * exist.  Otherwise, null will be returned if none have been created.
5806     * @see #setInputExtras(int)
5807     * @see EditorInfo#extras
5808     * @attr ref android.R.styleable#TextView_editorExtras
5809     */
5810    public Bundle getInputExtras(boolean create) {
5811        if (mEditor == null && !create) return null;
5812        createEditorIfNeeded();
5813        if (mEditor.mInputContentType == null) {
5814            if (!create) return null;
5815            mEditor.createInputContentTypeIfNeeded();
5816        }
5817        if (mEditor.mInputContentType.extras == null) {
5818            if (!create) return null;
5819            mEditor.mInputContentType.extras = new Bundle();
5820        }
5821        return mEditor.mInputContentType.extras;
5822    }
5823
5824    /**
5825     * Change "hint" locales associated with the text view, which will be reported to an IME with
5826     * {@link EditorInfo#hintLocales} when it has focus.
5827     *
5828     * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to
5829     * call {@link InputMethodManager#restartInput(View)}.</p>
5830     * @param hintLocales List of the languages that the user is supposed to switch to no matter
5831     * what input method subtype is currently used. Set {@code null} to clear the current "hint".
5832     * @see #getImeHintLocales()
5833     * @see android.view.inputmethod.EditorInfo#hintLocales
5834     */
5835    public void setImeHintLocales(@Nullable LocaleList hintLocales) {
5836        createEditorIfNeeded();
5837        mEditor.createInputContentTypeIfNeeded();
5838        mEditor.mInputContentType.imeHintLocales = hintLocales;
5839    }
5840
5841    /**
5842     * @return The current languages list "hint". {@code null} when no "hint" is available.
5843     * @see #setImeHintLocales(LocaleList)
5844     * @see android.view.inputmethod.EditorInfo#hintLocales
5845     */
5846    @Nullable
5847    public LocaleList getImeHintLocales() {
5848        if (mEditor == null) {
5849            return null;
5850        }
5851        if (mEditor.mInputContentType == null) {
5852            return null;
5853        }
5854        return mEditor.mInputContentType.imeHintLocales;
5855    }
5856
5857    /**
5858     * Returns the error message that was set to be displayed with
5859     * {@link #setError}, or <code>null</code> if no error was set
5860     * or if it the error was cleared by the widget after user input.
5861     */
5862    public CharSequence getError() {
5863        return mEditor == null ? null : mEditor.mError;
5864    }
5865
5866    /**
5867     * Sets the right-hand compound drawable of the TextView to the "error"
5868     * icon and sets an error message that will be displayed in a popup when
5869     * the TextView has focus.  The icon and error message will be reset to
5870     * null when any key events cause changes to the TextView's text.  If the
5871     * <code>error</code> is <code>null</code>, the error message and icon
5872     * will be cleared.
5873     */
5874    @android.view.RemotableViewMethod
5875    public void setError(CharSequence error) {
5876        if (error == null) {
5877            setError(null, null);
5878        } else {
5879            Drawable dr = getContext().getDrawable(
5880                    com.android.internal.R.drawable.indicator_input_error);
5881
5882            dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
5883            setError(error, dr);
5884        }
5885    }
5886
5887    /**
5888     * Sets the right-hand compound drawable of the TextView to the specified
5889     * icon and sets an error message that will be displayed in a popup when
5890     * the TextView has focus.  The icon and error message will be reset to
5891     * null when any key events cause changes to the TextView's text.  The
5892     * drawable must already have had {@link Drawable#setBounds} set on it.
5893     * If the <code>error</code> is <code>null</code>, the error message will
5894     * be cleared (and you should provide a <code>null</code> icon as well).
5895     */
5896    public void setError(CharSequence error, Drawable icon) {
5897        createEditorIfNeeded();
5898        mEditor.setError(error, icon);
5899        notifyViewAccessibilityStateChangedIfNeeded(
5900                AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
5901    }
5902
5903    @Override
5904    protected boolean setFrame(int l, int t, int r, int b) {
5905        boolean result = super.setFrame(l, t, r, b);
5906
5907        if (mEditor != null) mEditor.setFrame();
5908
5909        restartMarqueeIfNeeded();
5910
5911        return result;
5912    }
5913
5914    private void restartMarqueeIfNeeded() {
5915        if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
5916            mRestartMarquee = false;
5917            startMarquee();
5918        }
5919    }
5920
5921    /**
5922     * Sets the list of input filters that will be used if the buffer is
5923     * Editable. Has no effect otherwise.
5924     *
5925     * @attr ref android.R.styleable#TextView_maxLength
5926     */
5927    public void setFilters(InputFilter[] filters) {
5928        if (filters == null) {
5929            throw new IllegalArgumentException();
5930        }
5931
5932        mFilters = filters;
5933
5934        if (mText instanceof Editable) {
5935            setFilters((Editable) mText, filters);
5936        }
5937    }
5938
5939    /**
5940     * Sets the list of input filters on the specified Editable,
5941     * and includes mInput in the list if it is an InputFilter.
5942     */
5943    private void setFilters(Editable e, InputFilter[] filters) {
5944        if (mEditor != null) {
5945            final boolean undoFilter = mEditor.mUndoInputFilter != null;
5946            final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter;
5947            int num = 0;
5948            if (undoFilter) num++;
5949            if (keyFilter) num++;
5950            if (num > 0) {
5951                InputFilter[] nf = new InputFilter[filters.length + num];
5952
5953                System.arraycopy(filters, 0, nf, 0, filters.length);
5954                num = 0;
5955                if (undoFilter) {
5956                    nf[filters.length] = mEditor.mUndoInputFilter;
5957                    num++;
5958                }
5959                if (keyFilter) {
5960                    nf[filters.length + num] = (InputFilter) mEditor.mKeyListener;
5961                }
5962
5963                e.setFilters(nf);
5964                return;
5965            }
5966        }
5967        e.setFilters(filters);
5968    }
5969
5970    /**
5971     * Returns the current list of input filters.
5972     *
5973     * @attr ref android.R.styleable#TextView_maxLength
5974     */
5975    public InputFilter[] getFilters() {
5976        return mFilters;
5977    }
5978
5979    /////////////////////////////////////////////////////////////////////////
5980
5981    private int getBoxHeight(Layout l) {
5982        Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE;
5983        int padding = (l == mHintLayout)
5984                ? getCompoundPaddingTop() + getCompoundPaddingBottom()
5985                : getExtendedPaddingTop() + getExtendedPaddingBottom();
5986        return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom;
5987    }
5988
5989    int getVerticalOffset(boolean forceNormal) {
5990        int voffset = 0;
5991        final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
5992
5993        Layout l = mLayout;
5994        if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
5995            l = mHintLayout;
5996        }
5997
5998        if (gravity != Gravity.TOP) {
5999            int boxht = getBoxHeight(l);
6000            int textht = l.getHeight();
6001
6002            if (textht < boxht) {
6003                if (gravity == Gravity.BOTTOM) {
6004                    voffset = boxht - textht;
6005                } else { // (gravity == Gravity.CENTER_VERTICAL)
6006                    voffset = (boxht - textht) >> 1;
6007                }
6008            }
6009        }
6010        return voffset;
6011    }
6012
6013    private int getBottomVerticalOffset(boolean forceNormal) {
6014        int voffset = 0;
6015        final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
6016
6017        Layout l = mLayout;
6018        if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
6019            l = mHintLayout;
6020        }
6021
6022        if (gravity != Gravity.BOTTOM) {
6023            int boxht = getBoxHeight(l);
6024            int textht = l.getHeight();
6025
6026            if (textht < boxht) {
6027                if (gravity == Gravity.TOP) {
6028                    voffset = boxht - textht;
6029                } else { // (gravity == Gravity.CENTER_VERTICAL)
6030                    voffset = (boxht - textht) >> 1;
6031                }
6032            }
6033        }
6034        return voffset;
6035    }
6036
6037    void invalidateCursorPath() {
6038        if (mHighlightPathBogus) {
6039            invalidateCursor();
6040        } else {
6041            final int horizontalPadding = getCompoundPaddingLeft();
6042            final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
6043
6044            if (mEditor.mCursorCount == 0) {
6045                synchronized (TEMP_RECTF) {
6046                    /*
6047                     * The reason for this concern about the thickness of the
6048                     * cursor and doing the floor/ceil on the coordinates is that
6049                     * some EditTexts (notably textfields in the Browser) have
6050                     * anti-aliased text where not all the characters are
6051                     * necessarily at integer-multiple locations.  This should
6052                     * make sure the entire cursor gets invalidated instead of
6053                     * sometimes missing half a pixel.
6054                     */
6055                    float thick = (float) Math.ceil(mTextPaint.getStrokeWidth());
6056                    if (thick < 1.0f) {
6057                        thick = 1.0f;
6058                    }
6059
6060                    thick /= 2.0f;
6061
6062                    // mHighlightPath is guaranteed to be non null at that point.
6063                    mHighlightPath.computeBounds(TEMP_RECTF, false);
6064
6065                    invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick),
6066                            (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick),
6067                            (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick),
6068                            (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
6069                }
6070            } else {
6071                for (int i = 0; i < mEditor.mCursorCount; i++) {
6072                    Rect bounds = mEditor.mCursorDrawable[i].getBounds();
6073                    invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
6074                            bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
6075                }
6076            }
6077        }
6078    }
6079
6080    void invalidateCursor() {
6081        int where = getSelectionEnd();
6082
6083        invalidateCursor(where, where, where);
6084    }
6085
6086    private void invalidateCursor(int a, int b, int c) {
6087        if (a >= 0 || b >= 0 || c >= 0) {
6088            int start = Math.min(Math.min(a, b), c);
6089            int end = Math.max(Math.max(a, b), c);
6090            invalidateRegion(start, end, true /* Also invalidates blinking cursor */);
6091        }
6092    }
6093
6094    /**
6095     * Invalidates the region of text enclosed between the start and end text offsets.
6096     */
6097    void invalidateRegion(int start, int end, boolean invalidateCursor) {
6098        if (mLayout == null) {
6099            invalidate();
6100        } else {
6101            int lineStart = mLayout.getLineForOffset(start);
6102            int top = mLayout.getLineTop(lineStart);
6103
6104            // This is ridiculous, but the descent from the line above
6105            // can hang down into the line we really want to redraw,
6106            // so we have to invalidate part of the line above to make
6107            // sure everything that needs to be redrawn really is.
6108            // (But not the whole line above, because that would cause
6109            // the same problem with the descenders on the line above it!)
6110            if (lineStart > 0) {
6111                top -= mLayout.getLineDescent(lineStart - 1);
6112            }
6113
6114            int lineEnd;
6115
6116            if (start == end) {
6117                lineEnd = lineStart;
6118            } else {
6119                lineEnd = mLayout.getLineForOffset(end);
6120            }
6121
6122            int bottom = mLayout.getLineBottom(lineEnd);
6123
6124            // mEditor can be null in case selection is set programmatically.
6125            if (invalidateCursor && mEditor != null) {
6126                for (int i = 0; i < mEditor.mCursorCount; i++) {
6127                    Rect bounds = mEditor.mCursorDrawable[i].getBounds();
6128                    top = Math.min(top, bounds.top);
6129                    bottom = Math.max(bottom, bounds.bottom);
6130                }
6131            }
6132
6133            final int compoundPaddingLeft = getCompoundPaddingLeft();
6134            final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
6135
6136            int left, right;
6137            if (lineStart == lineEnd && !invalidateCursor) {
6138                left = (int) mLayout.getPrimaryHorizontal(start);
6139                right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0);
6140                left += compoundPaddingLeft;
6141                right += compoundPaddingLeft;
6142            } else {
6143                // Rectangle bounding box when the region spans several lines
6144                left = compoundPaddingLeft;
6145                right = getWidth() - getCompoundPaddingRight();
6146            }
6147
6148            invalidate(mScrollX + left, verticalPadding + top,
6149                    mScrollX + right, verticalPadding + bottom);
6150        }
6151    }
6152
6153    private void registerForPreDraw() {
6154        if (!mPreDrawRegistered) {
6155            getViewTreeObserver().addOnPreDrawListener(this);
6156            mPreDrawRegistered = true;
6157        }
6158    }
6159
6160    private void unregisterForPreDraw() {
6161        getViewTreeObserver().removeOnPreDrawListener(this);
6162        mPreDrawRegistered = false;
6163        mPreDrawListenerDetached = false;
6164    }
6165
6166    /**
6167     * {@inheritDoc}
6168     */
6169    @Override
6170    public boolean onPreDraw() {
6171        if (mLayout == null) {
6172            assumeLayout();
6173        }
6174
6175        if (mMovement != null) {
6176            /* This code also provides auto-scrolling when a cursor is moved using a
6177             * CursorController (insertion point or selection limits).
6178             * For selection, ensure start or end is visible depending on controller's state.
6179             */
6180            int curs = getSelectionEnd();
6181            // Do not create the controller if it is not already created.
6182            if (mEditor != null && mEditor.mSelectionModifierCursorController != null
6183                    && mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) {
6184                curs = getSelectionStart();
6185            }
6186
6187            /*
6188             * TODO: This should really only keep the end in view if
6189             * it already was before the text changed.  I'm not sure
6190             * of a good way to tell from here if it was.
6191             */
6192            if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
6193                curs = mText.length();
6194            }
6195
6196            if (curs >= 0) {
6197                bringPointIntoView(curs);
6198            }
6199        } else {
6200            bringTextIntoView();
6201        }
6202
6203        // This has to be checked here since:
6204        // - onFocusChanged cannot start it when focus is given to a view with selected text (after
6205        //   a screen rotation) since layout is not yet initialized at that point.
6206        if (mEditor != null && mEditor.mCreatedWithASelection) {
6207            mEditor.refreshTextActionMode();
6208            mEditor.mCreatedWithASelection = false;
6209        }
6210
6211        unregisterForPreDraw();
6212
6213        return true;
6214    }
6215
6216    @Override
6217    protected void onAttachedToWindow() {
6218        super.onAttachedToWindow();
6219
6220        if (mEditor != null) mEditor.onAttachedToWindow();
6221
6222        if (mPreDrawListenerDetached) {
6223            getViewTreeObserver().addOnPreDrawListener(this);
6224            mPreDrawListenerDetached = false;
6225        }
6226    }
6227
6228    /** @hide */
6229    @Override
6230    protected void onDetachedFromWindowInternal() {
6231        if (mPreDrawRegistered) {
6232            getViewTreeObserver().removeOnPreDrawListener(this);
6233            mPreDrawListenerDetached = true;
6234        }
6235
6236        resetResolvedDrawables();
6237
6238        if (mEditor != null) mEditor.onDetachedFromWindow();
6239
6240        super.onDetachedFromWindowInternal();
6241    }
6242
6243    @Override
6244    public void onScreenStateChanged(int screenState) {
6245        super.onScreenStateChanged(screenState);
6246        if (mEditor != null) mEditor.onScreenStateChanged(screenState);
6247    }
6248
6249    @Override
6250    protected boolean isPaddingOffsetRequired() {
6251        return mShadowRadius != 0 || mDrawables != null;
6252    }
6253
6254    @Override
6255    protected int getLeftPaddingOffset() {
6256        return getCompoundPaddingLeft() - mPaddingLeft
6257                + (int) Math.min(0, mShadowDx - mShadowRadius);
6258    }
6259
6260    @Override
6261    protected int getTopPaddingOffset() {
6262        return (int) Math.min(0, mShadowDy - mShadowRadius);
6263    }
6264
6265    @Override
6266    protected int getBottomPaddingOffset() {
6267        return (int) Math.max(0, mShadowDy + mShadowRadius);
6268    }
6269
6270    @Override
6271    protected int getRightPaddingOffset() {
6272        return -(getCompoundPaddingRight() - mPaddingRight)
6273                + (int) Math.max(0, mShadowDx + mShadowRadius);
6274    }
6275
6276    @Override
6277    protected boolean verifyDrawable(@NonNull Drawable who) {
6278        final boolean verified = super.verifyDrawable(who);
6279        if (!verified && mDrawables != null) {
6280            for (Drawable dr : mDrawables.mShowing) {
6281                if (who == dr) {
6282                    return true;
6283                }
6284            }
6285        }
6286        return verified;
6287    }
6288
6289    @Override
6290    public void jumpDrawablesToCurrentState() {
6291        super.jumpDrawablesToCurrentState();
6292        if (mDrawables != null) {
6293            for (Drawable dr : mDrawables.mShowing) {
6294                if (dr != null) {
6295                    dr.jumpToCurrentState();
6296                }
6297            }
6298        }
6299    }
6300
6301    @Override
6302    public void invalidateDrawable(@NonNull Drawable drawable) {
6303        boolean handled = false;
6304
6305        if (verifyDrawable(drawable)) {
6306            final Rect dirty = drawable.getBounds();
6307            int scrollX = mScrollX;
6308            int scrollY = mScrollY;
6309
6310            // IMPORTANT: The coordinates below are based on the coordinates computed
6311            // for each compound drawable in onDraw(). Make sure to update each section
6312            // accordingly.
6313            final TextView.Drawables drawables = mDrawables;
6314            if (drawables != null) {
6315                if (drawable == drawables.mShowing[Drawables.LEFT]) {
6316                    final int compoundPaddingTop = getCompoundPaddingTop();
6317                    final int compoundPaddingBottom = getCompoundPaddingBottom();
6318                    final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
6319
6320                    scrollX += mPaddingLeft;
6321                    scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
6322                    handled = true;
6323                } else if (drawable == drawables.mShowing[Drawables.RIGHT]) {
6324                    final int compoundPaddingTop = getCompoundPaddingTop();
6325                    final int compoundPaddingBottom = getCompoundPaddingBottom();
6326                    final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
6327
6328                    scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
6329                    scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
6330                    handled = true;
6331                } else if (drawable == drawables.mShowing[Drawables.TOP]) {
6332                    final int compoundPaddingLeft = getCompoundPaddingLeft();
6333                    final int compoundPaddingRight = getCompoundPaddingRight();
6334                    final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
6335
6336                    scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
6337                    scrollY += mPaddingTop;
6338                    handled = true;
6339                } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) {
6340                    final int compoundPaddingLeft = getCompoundPaddingLeft();
6341                    final int compoundPaddingRight = getCompoundPaddingRight();
6342                    final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
6343
6344                    scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
6345                    scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
6346                    handled = true;
6347                }
6348            }
6349
6350            if (handled) {
6351                invalidate(dirty.left + scrollX, dirty.top + scrollY,
6352                        dirty.right + scrollX, dirty.bottom + scrollY);
6353            }
6354        }
6355
6356        if (!handled) {
6357            super.invalidateDrawable(drawable);
6358        }
6359    }
6360
6361    @Override
6362    public boolean hasOverlappingRendering() {
6363        // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation
6364        return ((getBackground() != null && getBackground().getCurrent() != null)
6365                || mText instanceof Spannable || hasSelection()
6366                || isHorizontalFadingEdgeEnabled());
6367    }
6368
6369    /**
6370     *
6371     * Returns the state of the {@code textIsSelectable} flag (See
6372     * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag
6373     * to allow users to select and copy text in a non-editable TextView, the content of an
6374     * {@link EditText} can always be selected, independently of the value of this flag.
6375     * <p>
6376     *
6377     * @return True if the text displayed in this TextView can be selected by the user.
6378     *
6379     * @attr ref android.R.styleable#TextView_textIsSelectable
6380     */
6381    public boolean isTextSelectable() {
6382        return mEditor == null ? false : mEditor.mTextIsSelectable;
6383    }
6384
6385    /**
6386     * Sets whether the content of this view is selectable by the user. The default is
6387     * {@code false}, meaning that the content is not selectable.
6388     * <p>
6389     * When you use a TextView to display a useful piece of information to the user (such as a
6390     * contact's address), make it selectable, so that the user can select and copy its
6391     * content. You can also use set the XML attribute
6392     * {@link android.R.styleable#TextView_textIsSelectable} to "true".
6393     * <p>
6394     * When you call this method to set the value of {@code textIsSelectable}, it sets
6395     * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable},
6396     * and {@code longClickable} to the same value. These flags correspond to the attributes
6397     * {@link android.R.styleable#View_focusable android:focusable},
6398     * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode},
6399     * {@link android.R.styleable#View_clickable android:clickable}, and
6400     * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these
6401     * flags to a state you had set previously, call one or more of the following methods:
6402     * {@link #setFocusable(boolean) setFocusable()},
6403     * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()},
6404     * {@link #setClickable(boolean) setClickable()} or
6405     * {@link #setLongClickable(boolean) setLongClickable()}.
6406     *
6407     * @param selectable Whether the content of this TextView should be selectable.
6408     */
6409    public void setTextIsSelectable(boolean selectable) {
6410        if (!selectable && mEditor == null) return; // false is default value with no edit data
6411
6412        createEditorIfNeeded();
6413        if (mEditor.mTextIsSelectable == selectable) return;
6414
6415        mEditor.mTextIsSelectable = selectable;
6416        setFocusableInTouchMode(selectable);
6417        setFocusable(FOCUSABLE_AUTO);
6418        setClickable(selectable);
6419        setLongClickable(selectable);
6420
6421        // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
6422
6423        setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
6424        setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
6425
6426        // Called by setText above, but safer in case of future code changes
6427        mEditor.prepareCursorControllers();
6428    }
6429
6430    @Override
6431    protected int[] onCreateDrawableState(int extraSpace) {
6432        final int[] drawableState;
6433
6434        if (mSingleLine) {
6435            drawableState = super.onCreateDrawableState(extraSpace);
6436        } else {
6437            drawableState = super.onCreateDrawableState(extraSpace + 1);
6438            mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
6439        }
6440
6441        if (isTextSelectable()) {
6442            // Disable pressed state, which was introduced when TextView was made clickable.
6443            // Prevents text color change.
6444            // setClickable(false) would have a similar effect, but it also disables focus changes
6445            // and long press actions, which are both needed by text selection.
6446            final int length = drawableState.length;
6447            for (int i = 0; i < length; i++) {
6448                if (drawableState[i] == R.attr.state_pressed) {
6449                    final int[] nonPressedState = new int[length - 1];
6450                    System.arraycopy(drawableState, 0, nonPressedState, 0, i);
6451                    System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
6452                    return nonPressedState;
6453                }
6454            }
6455        }
6456
6457        return drawableState;
6458    }
6459
6460    private Path getUpdatedHighlightPath() {
6461        Path highlight = null;
6462        Paint highlightPaint = mHighlightPaint;
6463
6464        final int selStart = getSelectionStart();
6465        final int selEnd = getSelectionEnd();
6466        if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
6467            if (selStart == selEnd) {
6468                if (mEditor != null && mEditor.isCursorVisible()
6469                        && (SystemClock.uptimeMillis() - mEditor.mShowCursor)
6470                        % (2 * Editor.BLINK) < Editor.BLINK) {
6471                    if (mHighlightPathBogus) {
6472                        if (mHighlightPath == null) mHighlightPath = new Path();
6473                        mHighlightPath.reset();
6474                        mLayout.getCursorPath(selStart, mHighlightPath, mText);
6475                        mEditor.updateCursorsPositions();
6476                        mHighlightPathBogus = false;
6477                    }
6478
6479                    // XXX should pass to skin instead of drawing directly
6480                    highlightPaint.setColor(mCurTextColor);
6481                    highlightPaint.setStyle(Paint.Style.STROKE);
6482                    highlight = mHighlightPath;
6483                }
6484            } else {
6485                if (mHighlightPathBogus) {
6486                    if (mHighlightPath == null) mHighlightPath = new Path();
6487                    mHighlightPath.reset();
6488                    mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
6489                    mHighlightPathBogus = false;
6490                }
6491
6492                // XXX should pass to skin instead of drawing directly
6493                highlightPaint.setColor(mHighlightColor);
6494                highlightPaint.setStyle(Paint.Style.FILL);
6495
6496                highlight = mHighlightPath;
6497            }
6498        }
6499        return highlight;
6500    }
6501
6502    /**
6503     * @hide
6504     */
6505    public int getHorizontalOffsetForDrawables() {
6506        return 0;
6507    }
6508
6509    @Override
6510    protected void onDraw(Canvas canvas) {
6511        restartMarqueeIfNeeded();
6512
6513        // Draw the background for this view
6514        super.onDraw(canvas);
6515
6516        final int compoundPaddingLeft = getCompoundPaddingLeft();
6517        final int compoundPaddingTop = getCompoundPaddingTop();
6518        final int compoundPaddingRight = getCompoundPaddingRight();
6519        final int compoundPaddingBottom = getCompoundPaddingBottom();
6520        final int scrollX = mScrollX;
6521        final int scrollY = mScrollY;
6522        final int right = mRight;
6523        final int left = mLeft;
6524        final int bottom = mBottom;
6525        final int top = mTop;
6526        final boolean isLayoutRtl = isLayoutRtl();
6527        final int offset = getHorizontalOffsetForDrawables();
6528        final int leftOffset = isLayoutRtl ? 0 : offset;
6529        final int rightOffset = isLayoutRtl ? offset : 0;
6530
6531        final Drawables dr = mDrawables;
6532        if (dr != null) {
6533            /*
6534             * Compound, not extended, because the icon is not clipped
6535             * if the text height is smaller.
6536             */
6537
6538            int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
6539            int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
6540
6541            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
6542            // Make sure to update invalidateDrawable() when changing this code.
6543            if (dr.mShowing[Drawables.LEFT] != null) {
6544                canvas.save();
6545                canvas.translate(scrollX + mPaddingLeft + leftOffset,
6546                        scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2);
6547                dr.mShowing[Drawables.LEFT].draw(canvas);
6548                canvas.restore();
6549            }
6550
6551            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
6552            // Make sure to update invalidateDrawable() when changing this code.
6553            if (dr.mShowing[Drawables.RIGHT] != null) {
6554                canvas.save();
6555                canvas.translate(scrollX + right - left - mPaddingRight
6556                        - dr.mDrawableSizeRight - rightOffset,
6557                         scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
6558                dr.mShowing[Drawables.RIGHT].draw(canvas);
6559                canvas.restore();
6560            }
6561
6562            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
6563            // Make sure to update invalidateDrawable() when changing this code.
6564            if (dr.mShowing[Drawables.TOP] != null) {
6565                canvas.save();
6566                canvas.translate(scrollX + compoundPaddingLeft
6567                        + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
6568                dr.mShowing[Drawables.TOP].draw(canvas);
6569                canvas.restore();
6570            }
6571
6572            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
6573            // Make sure to update invalidateDrawable() when changing this code.
6574            if (dr.mShowing[Drawables.BOTTOM] != null) {
6575                canvas.save();
6576                canvas.translate(scrollX + compoundPaddingLeft
6577                        + (hspace - dr.mDrawableWidthBottom) / 2,
6578                         scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
6579                dr.mShowing[Drawables.BOTTOM].draw(canvas);
6580                canvas.restore();
6581            }
6582        }
6583
6584        int color = mCurTextColor;
6585
6586        if (mLayout == null) {
6587            assumeLayout();
6588        }
6589
6590        Layout layout = mLayout;
6591
6592        if (mHint != null && mText.length() == 0) {
6593            if (mHintTextColor != null) {
6594                color = mCurHintTextColor;
6595            }
6596
6597            layout = mHintLayout;
6598        }
6599
6600        mTextPaint.setColor(color);
6601        mTextPaint.drawableState = getDrawableState();
6602
6603        canvas.save();
6604        /*  Would be faster if we didn't have to do this. Can we chop the
6605            (displayable) text so that we don't need to do this ever?
6606        */
6607
6608        int extendedPaddingTop = getExtendedPaddingTop();
6609        int extendedPaddingBottom = getExtendedPaddingBottom();
6610
6611        final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
6612        final int maxScrollY = mLayout.getHeight() - vspace;
6613
6614        float clipLeft = compoundPaddingLeft + scrollX;
6615        float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
6616        float clipRight = right - left - getCompoundPaddingRight() + scrollX;
6617        float clipBottom = bottom - top + scrollY
6618                - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
6619
6620        if (mShadowRadius != 0) {
6621            clipLeft += Math.min(0, mShadowDx - mShadowRadius);
6622            clipRight += Math.max(0, mShadowDx + mShadowRadius);
6623
6624            clipTop += Math.min(0, mShadowDy - mShadowRadius);
6625            clipBottom += Math.max(0, mShadowDy + mShadowRadius);
6626        }
6627
6628        canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
6629
6630        int voffsetText = 0;
6631        int voffsetCursor = 0;
6632
6633        // translate in by our padding
6634        /* shortcircuit calling getVerticaOffset() */
6635        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
6636            voffsetText = getVerticalOffset(false);
6637            voffsetCursor = getVerticalOffset(true);
6638        }
6639        canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
6640
6641        final int layoutDirection = getLayoutDirection();
6642        final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
6643        if (isMarqueeFadeEnabled()) {
6644            if (!mSingleLine && getLineCount() == 1 && canMarquee()
6645                    && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
6646                final int width = mRight - mLeft;
6647                final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
6648                final float dx = mLayout.getLineRight(0) - (width - padding);
6649                canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
6650            }
6651
6652            if (mMarquee != null && mMarquee.isRunning()) {
6653                final float dx = -mMarquee.getScroll();
6654                canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
6655            }
6656        }
6657
6658        final int cursorOffsetVertical = voffsetCursor - voffsetText;
6659
6660        Path highlight = getUpdatedHighlightPath();
6661        if (mEditor != null) {
6662            mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
6663        } else {
6664            layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
6665        }
6666
6667        if (mMarquee != null && mMarquee.shouldDrawGhost()) {
6668            final float dx = mMarquee.getGhostOffset();
6669            canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
6670            layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
6671        }
6672
6673        canvas.restore();
6674    }
6675
6676    @Override
6677    public void getFocusedRect(Rect r) {
6678        if (mLayout == null) {
6679            super.getFocusedRect(r);
6680            return;
6681        }
6682
6683        int selEnd = getSelectionEnd();
6684        if (selEnd < 0) {
6685            super.getFocusedRect(r);
6686            return;
6687        }
6688
6689        int selStart = getSelectionStart();
6690        if (selStart < 0 || selStart >= selEnd) {
6691            int line = mLayout.getLineForOffset(selEnd);
6692            r.top = mLayout.getLineTop(line);
6693            r.bottom = mLayout.getLineBottom(line);
6694            r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
6695            r.right = r.left + 4;
6696        } else {
6697            int lineStart = mLayout.getLineForOffset(selStart);
6698            int lineEnd = mLayout.getLineForOffset(selEnd);
6699            r.top = mLayout.getLineTop(lineStart);
6700            r.bottom = mLayout.getLineBottom(lineEnd);
6701            if (lineStart == lineEnd) {
6702                r.left = (int) mLayout.getPrimaryHorizontal(selStart);
6703                r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
6704            } else {
6705                // Selection extends across multiple lines -- make the focused
6706                // rect cover the entire width.
6707                if (mHighlightPathBogus) {
6708                    if (mHighlightPath == null) mHighlightPath = new Path();
6709                    mHighlightPath.reset();
6710                    mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
6711                    mHighlightPathBogus = false;
6712                }
6713                synchronized (TEMP_RECTF) {
6714                    mHighlightPath.computeBounds(TEMP_RECTF, true);
6715                    r.left = (int) TEMP_RECTF.left - 1;
6716                    r.right = (int) TEMP_RECTF.right + 1;
6717                }
6718            }
6719        }
6720
6721        // Adjust for padding and gravity.
6722        int paddingLeft = getCompoundPaddingLeft();
6723        int paddingTop = getExtendedPaddingTop();
6724        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
6725            paddingTop += getVerticalOffset(false);
6726        }
6727        r.offset(paddingLeft, paddingTop);
6728        int paddingBottom = getExtendedPaddingBottom();
6729        r.bottom += paddingBottom;
6730    }
6731
6732    /**
6733     * Return the number of lines of text, or 0 if the internal Layout has not
6734     * been built.
6735     */
6736    public int getLineCount() {
6737        return mLayout != null ? mLayout.getLineCount() : 0;
6738    }
6739
6740    /**
6741     * Return the baseline for the specified line (0...getLineCount() - 1)
6742     * If bounds is not null, return the top, left, right, bottom extents
6743     * of the specified line in it. If the internal Layout has not been built,
6744     * return 0 and set bounds to (0, 0, 0, 0)
6745     * @param line which line to examine (0..getLineCount() - 1)
6746     * @param bounds Optional. If not null, it returns the extent of the line
6747     * @return the Y-coordinate of the baseline
6748     */
6749    public int getLineBounds(int line, Rect bounds) {
6750        if (mLayout == null) {
6751            if (bounds != null) {
6752                bounds.set(0, 0, 0, 0);
6753            }
6754            return 0;
6755        } else {
6756            int baseline = mLayout.getLineBounds(line, bounds);
6757
6758            int voffset = getExtendedPaddingTop();
6759            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
6760                voffset += getVerticalOffset(true);
6761            }
6762            if (bounds != null) {
6763                bounds.offset(getCompoundPaddingLeft(), voffset);
6764            }
6765            return baseline + voffset;
6766        }
6767    }
6768
6769    @Override
6770    public int getBaseline() {
6771        if (mLayout == null) {
6772            return super.getBaseline();
6773        }
6774
6775        return getBaselineOffset() + mLayout.getLineBaseline(0);
6776    }
6777
6778    int getBaselineOffset() {
6779        int voffset = 0;
6780        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
6781            voffset = getVerticalOffset(true);
6782        }
6783
6784        if (isLayoutModeOptical(mParent)) {
6785            voffset -= getOpticalInsets().top;
6786        }
6787
6788        return getExtendedPaddingTop() + voffset;
6789    }
6790
6791    /**
6792     * @hide
6793     */
6794    @Override
6795    protected int getFadeTop(boolean offsetRequired) {
6796        if (mLayout == null) return 0;
6797
6798        int voffset = 0;
6799        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
6800            voffset = getVerticalOffset(true);
6801        }
6802
6803        if (offsetRequired) voffset += getTopPaddingOffset();
6804
6805        return getExtendedPaddingTop() + voffset;
6806    }
6807
6808    /**
6809     * @hide
6810     */
6811    @Override
6812    protected int getFadeHeight(boolean offsetRequired) {
6813        return mLayout != null ? mLayout.getHeight() : 0;
6814    }
6815
6816    @Override
6817    public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
6818        if (mText instanceof Spannable && mLinksClickable) {
6819            final float x = event.getX(pointerIndex);
6820            final float y = event.getY(pointerIndex);
6821            final int offset = getOffsetForPosition(x, y);
6822            final ClickableSpan[] clickables = ((Spannable) mText).getSpans(offset, offset,
6823                    ClickableSpan.class);
6824            if (clickables.length > 0) {
6825                return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND);
6826            }
6827        }
6828        if (isTextSelectable() || isTextEditable()) {
6829            return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT);
6830        }
6831        return super.onResolvePointerIcon(event, pointerIndex);
6832    }
6833
6834    @Override
6835    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
6836        // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode,
6837        // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call
6838        // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event).
6839        if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) {
6840            return true;
6841        }
6842        return super.onKeyPreIme(keyCode, event);
6843    }
6844
6845    /**
6846     * @hide
6847     */
6848    public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) {
6849        // Do nothing unless mEditor is in text action mode.
6850        if (mEditor == null || mEditor.getTextActionMode() == null) {
6851            return false;
6852        }
6853
6854        if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
6855            KeyEvent.DispatcherState state = getKeyDispatcherState();
6856            if (state != null) {
6857                state.startTracking(event, this);
6858            }
6859            return true;
6860        } else if (event.getAction() == KeyEvent.ACTION_UP) {
6861            KeyEvent.DispatcherState state = getKeyDispatcherState();
6862            if (state != null) {
6863                state.handleUpEvent(event);
6864            }
6865            if (event.isTracking() && !event.isCanceled()) {
6866                stopTextActionMode();
6867                return true;
6868            }
6869        }
6870        return false;
6871    }
6872
6873    @Override
6874    public boolean onKeyDown(int keyCode, KeyEvent event) {
6875        final int which = doKeyDown(keyCode, event, null);
6876        if (which == KEY_EVENT_NOT_HANDLED) {
6877            return super.onKeyDown(keyCode, event);
6878        }
6879
6880        return true;
6881    }
6882
6883    @Override
6884    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
6885        KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
6886        final int which = doKeyDown(keyCode, down, event);
6887        if (which == KEY_EVENT_NOT_HANDLED) {
6888            // Go through default dispatching.
6889            return super.onKeyMultiple(keyCode, repeatCount, event);
6890        }
6891        if (which == KEY_EVENT_HANDLED) {
6892            // Consumed the whole thing.
6893            return true;
6894        }
6895
6896        repeatCount--;
6897
6898        // We are going to dispatch the remaining events to either the input
6899        // or movement method.  To do this, we will just send a repeated stream
6900        // of down and up events until we have done the complete repeatCount.
6901        // It would be nice if those interfaces had an onKeyMultiple() method,
6902        // but adding that is a more complicated change.
6903        KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
6904        if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) {
6905            // mEditor and mEditor.mInput are not null from doKeyDown
6906            mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up);
6907            while (--repeatCount > 0) {
6908                mEditor.mKeyListener.onKeyDown(this, (Editable) mText, keyCode, down);
6909                mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up);
6910            }
6911            hideErrorIfUnchanged();
6912
6913        } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) {
6914            // mMovement is not null from doKeyDown
6915            mMovement.onKeyUp(this, (Spannable) mText, keyCode, up);
6916            while (--repeatCount > 0) {
6917                mMovement.onKeyDown(this, (Spannable) mText, keyCode, down);
6918                mMovement.onKeyUp(this, (Spannable) mText, keyCode, up);
6919            }
6920        }
6921
6922        return true;
6923    }
6924
6925    /**
6926     * Returns true if pressing ENTER in this field advances focus instead
6927     * of inserting the character.  This is true mostly in single-line fields,
6928     * but also in mail addresses and subjects which will display on multiple
6929     * lines but where it doesn't make sense to insert newlines.
6930     */
6931    private boolean shouldAdvanceFocusOnEnter() {
6932        if (getKeyListener() == null) {
6933            return false;
6934        }
6935
6936        if (mSingleLine) {
6937            return true;
6938        }
6939
6940        if (mEditor != null
6941                && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
6942                        == EditorInfo.TYPE_CLASS_TEXT) {
6943            int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
6944            if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
6945                    || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
6946                return true;
6947            }
6948        }
6949
6950        return false;
6951    }
6952
6953    /**
6954     * Returns true if pressing TAB in this field advances focus instead
6955     * of inserting the character.  Insert tabs only in multi-line editors.
6956     */
6957    private boolean shouldAdvanceFocusOnTab() {
6958        if (getKeyListener() != null && !mSingleLine && mEditor != null
6959                && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
6960                        == EditorInfo.TYPE_CLASS_TEXT) {
6961            int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
6962            if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
6963                    || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
6964                return false;
6965            }
6966        }
6967        return true;
6968    }
6969
6970    private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
6971        if (!isEnabled()) {
6972            return KEY_EVENT_NOT_HANDLED;
6973        }
6974
6975        // If this is the initial keydown, we don't want to prevent a movement away from this view.
6976        // While this shouldn't be necessary because any time we're preventing default movement we
6977        // should be restricting the focus to remain within this view, thus we'll also receive
6978        // the key up event, occasionally key up events will get dropped and we don't want to
6979        // prevent the user from traversing out of this on the next key down.
6980        if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
6981            mPreventDefaultMovement = false;
6982        }
6983
6984        switch (keyCode) {
6985            case KeyEvent.KEYCODE_ENTER:
6986                if (event.hasNoModifiers()) {
6987                    // When mInputContentType is set, we know that we are
6988                    // running in a "modern" cupcake environment, so don't need
6989                    // to worry about the application trying to capture
6990                    // enter key events.
6991                    if (mEditor != null && mEditor.mInputContentType != null) {
6992                        // If there is an action listener, given them a
6993                        // chance to consume the event.
6994                        if (mEditor.mInputContentType.onEditorActionListener != null
6995                                && mEditor.mInputContentType.onEditorActionListener.onEditorAction(
6996                                        this, EditorInfo.IME_NULL, event)) {
6997                            mEditor.mInputContentType.enterDown = true;
6998                            // We are consuming the enter key for them.
6999                            return KEY_EVENT_HANDLED;
7000                        }
7001                    }
7002
7003                    // If our editor should move focus when enter is pressed, or
7004                    // this is a generated event from an IME action button, then
7005                    // don't let it be inserted into the text.
7006                    if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
7007                            || shouldAdvanceFocusOnEnter()) {
7008                        if (hasOnClickListeners()) {
7009                            return KEY_EVENT_NOT_HANDLED;
7010                        }
7011                        return KEY_EVENT_HANDLED;
7012                    }
7013                }
7014                break;
7015
7016            case KeyEvent.KEYCODE_DPAD_CENTER:
7017                if (event.hasNoModifiers()) {
7018                    if (shouldAdvanceFocusOnEnter()) {
7019                        return KEY_EVENT_NOT_HANDLED;
7020                    }
7021                }
7022                break;
7023
7024            case KeyEvent.KEYCODE_TAB:
7025                if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
7026                    if (shouldAdvanceFocusOnTab()) {
7027                        return KEY_EVENT_NOT_HANDLED;
7028                    }
7029                }
7030                break;
7031
7032                // Has to be done on key down (and not on key up) to correctly be intercepted.
7033            case KeyEvent.KEYCODE_BACK:
7034                if (mEditor != null && mEditor.getTextActionMode() != null) {
7035                    stopTextActionMode();
7036                    return KEY_EVENT_HANDLED;
7037                }
7038                break;
7039
7040            case KeyEvent.KEYCODE_CUT:
7041                if (event.hasNoModifiers() && canCut()) {
7042                    if (onTextContextMenuItem(ID_CUT)) {
7043                        return KEY_EVENT_HANDLED;
7044                    }
7045                }
7046                break;
7047
7048            case KeyEvent.KEYCODE_COPY:
7049                if (event.hasNoModifiers() && canCopy()) {
7050                    if (onTextContextMenuItem(ID_COPY)) {
7051                        return KEY_EVENT_HANDLED;
7052                    }
7053                }
7054                break;
7055
7056            case KeyEvent.KEYCODE_PASTE:
7057                if (event.hasNoModifiers() && canPaste()) {
7058                    if (onTextContextMenuItem(ID_PASTE)) {
7059                        return KEY_EVENT_HANDLED;
7060                    }
7061                }
7062                break;
7063        }
7064
7065        if (mEditor != null && mEditor.mKeyListener != null) {
7066            boolean doDown = true;
7067            if (otherEvent != null) {
7068                try {
7069                    beginBatchEdit();
7070                    final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText,
7071                            otherEvent);
7072                    hideErrorIfUnchanged();
7073                    doDown = false;
7074                    if (handled) {
7075                        return KEY_EVENT_HANDLED;
7076                    }
7077                } catch (AbstractMethodError e) {
7078                    // onKeyOther was added after 1.0, so if it isn't
7079                    // implemented we need to try to dispatch as a regular down.
7080                } finally {
7081                    endBatchEdit();
7082                }
7083            }
7084
7085            if (doDown) {
7086                beginBatchEdit();
7087                final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText,
7088                        keyCode, event);
7089                endBatchEdit();
7090                hideErrorIfUnchanged();
7091                if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER;
7092            }
7093        }
7094
7095        // bug 650865: sometimes we get a key event before a layout.
7096        // don't try to move around if we don't know the layout.
7097
7098        if (mMovement != null && mLayout != null) {
7099            boolean doDown = true;
7100            if (otherEvent != null) {
7101                try {
7102                    boolean handled = mMovement.onKeyOther(this, (Spannable) mText,
7103                            otherEvent);
7104                    doDown = false;
7105                    if (handled) {
7106                        return KEY_EVENT_HANDLED;
7107                    }
7108                } catch (AbstractMethodError e) {
7109                    // onKeyOther was added after 1.0, so if it isn't
7110                    // implemented we need to try to dispatch as a regular down.
7111                }
7112            }
7113            if (doDown) {
7114                if (mMovement.onKeyDown(this, (Spannable) mText, keyCode, event)) {
7115                    if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
7116                        mPreventDefaultMovement = true;
7117                    }
7118                    return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD;
7119                }
7120            }
7121        }
7122
7123        return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode)
7124                ? KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED;
7125    }
7126
7127    /**
7128     * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
7129     * can be recorded.
7130     * @hide
7131     */
7132    public void resetErrorChangedFlag() {
7133        /*
7134         * Keep track of what the error was before doing the input
7135         * so that if an input filter changed the error, we leave
7136         * that error showing.  Otherwise, we take down whatever
7137         * error was showing when the user types something.
7138         */
7139        if (mEditor != null) mEditor.mErrorWasChanged = false;
7140    }
7141
7142    /**
7143     * @hide
7144     */
7145    public void hideErrorIfUnchanged() {
7146        if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) {
7147            setError(null, null);
7148        }
7149    }
7150
7151    @Override
7152    public boolean onKeyUp(int keyCode, KeyEvent event) {
7153        if (!isEnabled()) {
7154            return super.onKeyUp(keyCode, event);
7155        }
7156
7157        if (!KeyEvent.isModifierKey(keyCode)) {
7158            mPreventDefaultMovement = false;
7159        }
7160
7161        switch (keyCode) {
7162            case KeyEvent.KEYCODE_DPAD_CENTER:
7163                if (event.hasNoModifiers()) {
7164                    /*
7165                     * If there is a click listener, just call through to
7166                     * super, which will invoke it.
7167                     *
7168                     * If there isn't a click listener, try to show the soft
7169                     * input method.  (It will also
7170                     * call performClick(), but that won't do anything in
7171                     * this case.)
7172                     */
7173                    if (!hasOnClickListeners()) {
7174                        if (mMovement != null && mText instanceof Editable
7175                                && mLayout != null && onCheckIsTextEditor()) {
7176                            InputMethodManager imm = InputMethodManager.peekInstance();
7177                            viewClicked(imm);
7178                            if (imm != null && getShowSoftInputOnFocus()) {
7179                                imm.showSoftInput(this, 0);
7180                            }
7181                        }
7182                    }
7183                }
7184                return super.onKeyUp(keyCode, event);
7185
7186            case KeyEvent.KEYCODE_ENTER:
7187                if (event.hasNoModifiers()) {
7188                    if (mEditor != null && mEditor.mInputContentType != null
7189                            && mEditor.mInputContentType.onEditorActionListener != null
7190                            && mEditor.mInputContentType.enterDown) {
7191                        mEditor.mInputContentType.enterDown = false;
7192                        if (mEditor.mInputContentType.onEditorActionListener.onEditorAction(
7193                                this, EditorInfo.IME_NULL, event)) {
7194                            return true;
7195                        }
7196                    }
7197
7198                    if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
7199                            || shouldAdvanceFocusOnEnter()) {
7200                        /*
7201                         * If there is a click listener, just call through to
7202                         * super, which will invoke it.
7203                         *
7204                         * If there isn't a click listener, try to advance focus,
7205                         * but still call through to super, which will reset the
7206                         * pressed state and longpress state.  (It will also
7207                         * call performClick(), but that won't do anything in
7208                         * this case.)
7209                         */
7210                        if (!hasOnClickListeners()) {
7211                            View v = focusSearch(FOCUS_DOWN);
7212
7213                            if (v != null) {
7214                                if (!v.requestFocus(FOCUS_DOWN)) {
7215                                    throw new IllegalStateException("focus search returned a view "
7216                                            + "that wasn't able to take focus!");
7217                                }
7218
7219                                /*
7220                                 * Return true because we handled the key; super
7221                                 * will return false because there was no click
7222                                 * listener.
7223                                 */
7224                                super.onKeyUp(keyCode, event);
7225                                return true;
7226                            } else if ((event.getFlags()
7227                                    & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
7228                                // No target for next focus, but make sure the IME
7229                                // if this came from it.
7230                                InputMethodManager imm = InputMethodManager.peekInstance();
7231                                if (imm != null && imm.isActive(this)) {
7232                                    imm.hideSoftInputFromWindow(getWindowToken(), 0);
7233                                }
7234                            }
7235                        }
7236                    }
7237                    return super.onKeyUp(keyCode, event);
7238                }
7239                break;
7240        }
7241
7242        if (mEditor != null && mEditor.mKeyListener != null) {
7243            if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event)) {
7244                return true;
7245            }
7246        }
7247
7248        if (mMovement != null && mLayout != null) {
7249            if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event)) {
7250                return true;
7251            }
7252        }
7253
7254        return super.onKeyUp(keyCode, event);
7255    }
7256
7257    @Override
7258    public boolean onCheckIsTextEditor() {
7259        return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL;
7260    }
7261
7262    @Override
7263    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
7264        if (onCheckIsTextEditor() && isEnabled()) {
7265            mEditor.createInputMethodStateIfNeeded();
7266            outAttrs.inputType = getInputType();
7267            if (mEditor.mInputContentType != null) {
7268                outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
7269                outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
7270                outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
7271                outAttrs.actionId = mEditor.mInputContentType.imeActionId;
7272                outAttrs.extras = mEditor.mInputContentType.extras;
7273                outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales;
7274            } else {
7275                outAttrs.imeOptions = EditorInfo.IME_NULL;
7276                outAttrs.hintLocales = null;
7277            }
7278            if (focusSearch(FOCUS_DOWN) != null) {
7279                outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
7280            }
7281            if (focusSearch(FOCUS_UP) != null) {
7282                outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
7283            }
7284            if ((outAttrs.imeOptions & EditorInfo.IME_MASK_ACTION)
7285                    == EditorInfo.IME_ACTION_UNSPECIFIED) {
7286                if ((outAttrs.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
7287                    // An action has not been set, but the enter key will move to
7288                    // the next focus, so set the action to that.
7289                    outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
7290                } else {
7291                    // An action has not been set, and there is no focus to move
7292                    // to, so let's just supply a "done" action.
7293                    outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
7294                }
7295                if (!shouldAdvanceFocusOnEnter()) {
7296                    outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
7297                }
7298            }
7299            if (isMultilineInputType(outAttrs.inputType)) {
7300                // Multi-line text editors should always show an enter key.
7301                outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
7302            }
7303            outAttrs.hintText = mHint;
7304            if (mText instanceof Editable) {
7305                InputConnection ic = new EditableInputConnection(this);
7306                outAttrs.initialSelStart = getSelectionStart();
7307                outAttrs.initialSelEnd = getSelectionEnd();
7308                outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
7309                return ic;
7310            }
7311        }
7312        return null;
7313    }
7314
7315    /**
7316     * If this TextView contains editable content, extract a portion of it
7317     * based on the information in <var>request</var> in to <var>outText</var>.
7318     * @return Returns true if the text was successfully extracted, else false.
7319     */
7320    public boolean extractText(ExtractedTextRequest request, ExtractedText outText) {
7321        createEditorIfNeeded();
7322        return mEditor.extractText(request, outText);
7323    }
7324
7325    /**
7326     * This is used to remove all style-impacting spans from text before new
7327     * extracted text is being replaced into it, so that we don't have any
7328     * lingering spans applied during the replace.
7329     */
7330    static void removeParcelableSpans(Spannable spannable, int start, int end) {
7331        Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
7332        int i = spans.length;
7333        while (i > 0) {
7334            i--;
7335            spannable.removeSpan(spans[i]);
7336        }
7337    }
7338
7339    /**
7340     * Apply to this text view the given extracted text, as previously
7341     * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
7342     */
7343    public void setExtractedText(ExtractedText text) {
7344        Editable content = getEditableText();
7345        if (text.text != null) {
7346            if (content == null) {
7347                setText(text.text, TextView.BufferType.EDITABLE);
7348            } else {
7349                int start = 0;
7350                int end = content.length();
7351
7352                if (text.partialStartOffset >= 0) {
7353                    final int N = content.length();
7354                    start = text.partialStartOffset;
7355                    if (start > N) start = N;
7356                    end = text.partialEndOffset;
7357                    if (end > N) end = N;
7358                }
7359
7360                removeParcelableSpans(content, start, end);
7361                if (TextUtils.equals(content.subSequence(start, end), text.text)) {
7362                    if (text.text instanceof Spanned) {
7363                        // OK to copy spans only.
7364                        TextUtils.copySpansFrom((Spanned) text.text, 0, end - start,
7365                                Object.class, content, start);
7366                    }
7367                } else {
7368                    content.replace(start, end, text.text);
7369                }
7370            }
7371        }
7372
7373        // Now set the selection position...  make sure it is in range, to
7374        // avoid crashes.  If this is a partial update, it is possible that
7375        // the underlying text may have changed, causing us problems here.
7376        // Also we just don't want to trust clients to do the right thing.
7377        Spannable sp = (Spannable) getText();
7378        final int N = sp.length();
7379        int start = text.selectionStart;
7380        if (start < 0) {
7381            start = 0;
7382        } else if (start > N) {
7383            start = N;
7384        }
7385        int end = text.selectionEnd;
7386        if (end < 0) {
7387            end = 0;
7388        } else if (end > N) {
7389            end = N;
7390        }
7391        Selection.setSelection(sp, start, end);
7392
7393        // Finally, update the selection mode.
7394        if ((text.flags & ExtractedText.FLAG_SELECTING) != 0) {
7395            MetaKeyKeyListener.startSelecting(this, sp);
7396        } else {
7397            MetaKeyKeyListener.stopSelecting(this, sp);
7398        }
7399    }
7400
7401    /**
7402     * @hide
7403     */
7404    public void setExtracting(ExtractedTextRequest req) {
7405        if (mEditor.mInputMethodState != null) {
7406            mEditor.mInputMethodState.mExtractedTextRequest = req;
7407        }
7408        // This would stop a possible selection mode, but no such mode is started in case
7409        // extracted mode will start. Some text is selected though, and will trigger an action mode
7410        // in the extracted view.
7411        mEditor.hideCursorAndSpanControllers();
7412        stopTextActionMode();
7413        if (mEditor.mSelectionModifierCursorController != null) {
7414            mEditor.mSelectionModifierCursorController.resetTouchOffsets();
7415        }
7416    }
7417
7418    /**
7419     * Called by the framework in response to a text completion from
7420     * the current input method, provided by it calling
7421     * {@link InputConnection#commitCompletion
7422     * InputConnection.commitCompletion()}.  The default implementation does
7423     * nothing; text views that are supporting auto-completion should override
7424     * this to do their desired behavior.
7425     *
7426     * @param text The auto complete text the user has selected.
7427     */
7428    public void onCommitCompletion(CompletionInfo text) {
7429        // intentionally empty
7430    }
7431
7432    /**
7433     * Called by the framework in response to a text auto-correction (such as fixing a typo using a
7434     * a dictionary) from the current input method, provided by it calling
7435     * {@link InputConnection#commitCorrection(CorrectionInfo) InputConnection.commitCorrection()}.
7436     * The default implementation flashes the background of the corrected word to provide
7437     * feedback to the user.
7438     *
7439     * @param info The auto correct info about the text that was corrected.
7440     */
7441    public void onCommitCorrection(CorrectionInfo info) {
7442        if (mEditor != null) mEditor.onCommitCorrection(info);
7443    }
7444
7445    public void beginBatchEdit() {
7446        if (mEditor != null) mEditor.beginBatchEdit();
7447    }
7448
7449    public void endBatchEdit() {
7450        if (mEditor != null) mEditor.endBatchEdit();
7451    }
7452
7453    /**
7454     * Called by the framework in response to a request to begin a batch
7455     * of edit operations through a call to link {@link #beginBatchEdit()}.
7456     */
7457    public void onBeginBatchEdit() {
7458        // intentionally empty
7459    }
7460
7461    /**
7462     * Called by the framework in response to a request to end a batch
7463     * of edit operations through a call to link {@link #endBatchEdit}.
7464     */
7465    public void onEndBatchEdit() {
7466        // intentionally empty
7467    }
7468
7469    /**
7470     * Called by the framework in response to a private command from the
7471     * current method, provided by it calling
7472     * {@link InputConnection#performPrivateCommand
7473     * InputConnection.performPrivateCommand()}.
7474     *
7475     * @param action The action name of the command.
7476     * @param data Any additional data for the command.  This may be null.
7477     * @return Return true if you handled the command, else false.
7478     */
7479    public boolean onPrivateIMECommand(String action, Bundle data) {
7480        return false;
7481    }
7482
7483    private void nullLayouts() {
7484        if (mLayout instanceof BoringLayout && mSavedLayout == null) {
7485            mSavedLayout = (BoringLayout) mLayout;
7486        }
7487        if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
7488            mSavedHintLayout = (BoringLayout) mHintLayout;
7489        }
7490
7491        mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
7492
7493        mBoring = mHintBoring = null;
7494
7495        // Since it depends on the value of mLayout
7496        if (mEditor != null) mEditor.prepareCursorControllers();
7497    }
7498
7499    /**
7500     * Make a new Layout based on the already-measured size of the view,
7501     * on the assumption that it was measured correctly at some point.
7502     */
7503    private void assumeLayout() {
7504        int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
7505
7506        if (width < 1) {
7507            width = 0;
7508        }
7509
7510        int physicalWidth = width;
7511
7512        if (mHorizontallyScrolling) {
7513            width = VERY_WIDE;
7514        }
7515
7516        makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
7517                      physicalWidth, false);
7518    }
7519
7520    private Layout.Alignment getLayoutAlignment() {
7521        Layout.Alignment alignment;
7522        switch (getTextAlignment()) {
7523            case TEXT_ALIGNMENT_GRAVITY:
7524                switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
7525                    case Gravity.START:
7526                        alignment = Layout.Alignment.ALIGN_NORMAL;
7527                        break;
7528                    case Gravity.END:
7529                        alignment = Layout.Alignment.ALIGN_OPPOSITE;
7530                        break;
7531                    case Gravity.LEFT:
7532                        alignment = Layout.Alignment.ALIGN_LEFT;
7533                        break;
7534                    case Gravity.RIGHT:
7535                        alignment = Layout.Alignment.ALIGN_RIGHT;
7536                        break;
7537                    case Gravity.CENTER_HORIZONTAL:
7538                        alignment = Layout.Alignment.ALIGN_CENTER;
7539                        break;
7540                    default:
7541                        alignment = Layout.Alignment.ALIGN_NORMAL;
7542                        break;
7543                }
7544                break;
7545            case TEXT_ALIGNMENT_TEXT_START:
7546                alignment = Layout.Alignment.ALIGN_NORMAL;
7547                break;
7548            case TEXT_ALIGNMENT_TEXT_END:
7549                alignment = Layout.Alignment.ALIGN_OPPOSITE;
7550                break;
7551            case TEXT_ALIGNMENT_CENTER:
7552                alignment = Layout.Alignment.ALIGN_CENTER;
7553                break;
7554            case TEXT_ALIGNMENT_VIEW_START:
7555                alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL)
7556                        ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
7557                break;
7558            case TEXT_ALIGNMENT_VIEW_END:
7559                alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL)
7560                        ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
7561                break;
7562            case TEXT_ALIGNMENT_INHERIT:
7563                // This should never happen as we have already resolved the text alignment
7564                // but better safe than sorry so we just fall through
7565            default:
7566                alignment = Layout.Alignment.ALIGN_NORMAL;
7567                break;
7568        }
7569        return alignment;
7570    }
7571
7572    /**
7573     * The width passed in is now the desired layout width,
7574     * not the full view width with padding.
7575     * {@hide}
7576     */
7577    protected void makeNewLayout(int wantWidth, int hintWidth,
7578                                 BoringLayout.Metrics boring,
7579                                 BoringLayout.Metrics hintBoring,
7580                                 int ellipsisWidth, boolean bringIntoView) {
7581        stopMarquee();
7582
7583        // Update "old" cached values
7584        mOldMaximum = mMaximum;
7585        mOldMaxMode = mMaxMode;
7586
7587        mHighlightPathBogus = true;
7588
7589        if (wantWidth < 0) {
7590            wantWidth = 0;
7591        }
7592        if (hintWidth < 0) {
7593            hintWidth = 0;
7594        }
7595
7596        Layout.Alignment alignment = getLayoutAlignment();
7597        final boolean testDirChange = mSingleLine && mLayout != null
7598                && (alignment == Layout.Alignment.ALIGN_NORMAL
7599                        || alignment == Layout.Alignment.ALIGN_OPPOSITE);
7600        int oldDir = 0;
7601        if (testDirChange) oldDir = mLayout.getParagraphDirection(0);
7602        boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
7603        final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE
7604                && mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
7605        TruncateAt effectiveEllipsize = mEllipsize;
7606        if (mEllipsize == TruncateAt.MARQUEE
7607                && mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
7608            effectiveEllipsize = TruncateAt.END_SMALL;
7609        }
7610
7611        if (mTextDir == null) {
7612            mTextDir = getTextDirectionHeuristic();
7613        }
7614
7615        mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
7616                effectiveEllipsize, effectiveEllipsize == mEllipsize);
7617        if (switchEllipsize) {
7618            TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE
7619                    ? TruncateAt.END : TruncateAt.MARQUEE;
7620            mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
7621                    shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
7622        }
7623
7624        shouldEllipsize = mEllipsize != null;
7625        mHintLayout = null;
7626
7627        if (mHint != null) {
7628            if (shouldEllipsize) hintWidth = wantWidth;
7629
7630            if (hintBoring == UNKNOWN_BORING) {
7631                hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
7632                                                   mHintBoring);
7633                if (hintBoring != null) {
7634                    mHintBoring = hintBoring;
7635                }
7636            }
7637
7638            if (hintBoring != null) {
7639                if (hintBoring.width <= hintWidth
7640                        && (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
7641                    if (mSavedHintLayout != null) {
7642                        mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint,
7643                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
7644                                hintBoring, mIncludePad);
7645                    } else {
7646                        mHintLayout = BoringLayout.make(mHint, mTextPaint,
7647                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
7648                                hintBoring, mIncludePad);
7649                    }
7650
7651                    mSavedHintLayout = (BoringLayout) mHintLayout;
7652                } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
7653                    if (mSavedHintLayout != null) {
7654                        mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint,
7655                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
7656                                hintBoring, mIncludePad, mEllipsize,
7657                                ellipsisWidth);
7658                    } else {
7659                        mHintLayout = BoringLayout.make(mHint, mTextPaint,
7660                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
7661                                hintBoring, mIncludePad, mEllipsize,
7662                                ellipsisWidth);
7663                    }
7664                }
7665            }
7666            // TODO: code duplication with makeSingleLayout()
7667            if (mHintLayout == null) {
7668                StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0,
7669                        mHint.length(), mTextPaint, hintWidth)
7670                        .setAlignment(alignment)
7671                        .setTextDirection(mTextDir)
7672                        .setLineSpacing(mSpacingAdd, mSpacingMult)
7673                        .setIncludePad(mIncludePad)
7674                        .setBreakStrategy(mBreakStrategy)
7675                        .setHyphenationFrequency(mHyphenationFrequency)
7676                        .setJustify(mJustify)
7677                        .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
7678                if (shouldEllipsize) {
7679                    builder.setEllipsize(mEllipsize)
7680                            .setEllipsizedWidth(ellipsisWidth);
7681                }
7682                mHintLayout = builder.build();
7683            }
7684        }
7685
7686        if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) {
7687            registerForPreDraw();
7688        }
7689
7690        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7691            if (!compressText(ellipsisWidth)) {
7692                final int height = mLayoutParams.height;
7693                // If the size of the view does not depend on the size of the text, try to
7694                // start the marquee immediately
7695                if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
7696                    startMarquee();
7697                } else {
7698                    // Defer the start of the marquee until we know our width (see setFrame())
7699                    mRestartMarquee = true;
7700                }
7701            }
7702        }
7703
7704        // CursorControllers need a non-null mLayout
7705        if (mEditor != null) mEditor.prepareCursorControllers();
7706    }
7707
7708    /**
7709     * @hide
7710     */
7711    protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
7712            Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
7713            boolean useSaved) {
7714        Layout result = null;
7715        if (mText instanceof Spannable) {
7716            result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth,
7717                    alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad,
7718                    mBreakStrategy, mHyphenationFrequency, mJustify,
7719                    getKeyListener() == null ? effectiveEllipsize : null, ellipsisWidth);
7720        } else {
7721            if (boring == UNKNOWN_BORING) {
7722                boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
7723                if (boring != null) {
7724                    mBoring = boring;
7725                }
7726            }
7727
7728            if (boring != null) {
7729                if (boring.width <= wantWidth
7730                        && (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
7731                    if (useSaved && mSavedLayout != null) {
7732                        result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
7733                                wantWidth, alignment, mSpacingMult, mSpacingAdd,
7734                                boring, mIncludePad);
7735                    } else {
7736                        result = BoringLayout.make(mTransformed, mTextPaint,
7737                                wantWidth, alignment, mSpacingMult, mSpacingAdd,
7738                                boring, mIncludePad);
7739                    }
7740
7741                    if (useSaved) {
7742                        mSavedLayout = (BoringLayout) result;
7743                    }
7744                } else if (shouldEllipsize && boring.width <= wantWidth) {
7745                    if (useSaved && mSavedLayout != null) {
7746                        result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
7747                                wantWidth, alignment, mSpacingMult, mSpacingAdd,
7748                                boring, mIncludePad, effectiveEllipsize,
7749                                ellipsisWidth);
7750                    } else {
7751                        result = BoringLayout.make(mTransformed, mTextPaint,
7752                                wantWidth, alignment, mSpacingMult, mSpacingAdd,
7753                                boring, mIncludePad, effectiveEllipsize,
7754                                ellipsisWidth);
7755                    }
7756                }
7757            }
7758        }
7759        if (result == null) {
7760            StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
7761                    0, mTransformed.length(), mTextPaint, wantWidth)
7762                    .setAlignment(alignment)
7763                    .setTextDirection(mTextDir)
7764                    .setLineSpacing(mSpacingAdd, mSpacingMult)
7765                    .setIncludePad(mIncludePad)
7766                    .setBreakStrategy(mBreakStrategy)
7767                    .setHyphenationFrequency(mHyphenationFrequency)
7768                    .setJustify(mJustify)
7769                    .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
7770            if (shouldEllipsize) {
7771                builder.setEllipsize(effectiveEllipsize)
7772                        .setEllipsizedWidth(ellipsisWidth);
7773            }
7774            // TODO: explore always setting maxLines
7775            result = builder.build();
7776        }
7777        return result;
7778    }
7779
7780    private boolean compressText(float width) {
7781        if (isHardwareAccelerated()) return false;
7782
7783        // Only compress the text if it hasn't been compressed by the previous pass
7784        if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX
7785                && mTextPaint.getTextScaleX() == 1.0f) {
7786            final float textWidth = mLayout.getLineWidth(0);
7787            final float overflow = (textWidth + 1.0f - width) / width;
7788            if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
7789                mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
7790                post(new Runnable() {
7791                    public void run() {
7792                        requestLayout();
7793                    }
7794                });
7795                return true;
7796            }
7797        }
7798
7799        return false;
7800    }
7801
7802    private static int desired(Layout layout) {
7803        int n = layout.getLineCount();
7804        CharSequence text = layout.getText();
7805        float max = 0;
7806
7807        // if any line was wrapped, we can't use it.
7808        // but it's ok for the last line not to have a newline
7809
7810        for (int i = 0; i < n - 1; i++) {
7811            if (text.charAt(layout.getLineEnd(i) - 1) != '\n') {
7812                return -1;
7813            }
7814        }
7815
7816        for (int i = 0; i < n; i++) {
7817            max = Math.max(max, layout.getLineWidth(i));
7818        }
7819
7820        return (int) Math.ceil(max);
7821    }
7822
7823    /**
7824     * Set whether the TextView includes extra top and bottom padding to make
7825     * room for accents that go above the normal ascent and descent.
7826     * The default is true.
7827     *
7828     * @see #getIncludeFontPadding()
7829     *
7830     * @attr ref android.R.styleable#TextView_includeFontPadding
7831     */
7832    public void setIncludeFontPadding(boolean includepad) {
7833        if (mIncludePad != includepad) {
7834            mIncludePad = includepad;
7835
7836            if (mLayout != null) {
7837                nullLayouts();
7838                requestLayout();
7839                invalidate();
7840            }
7841        }
7842    }
7843
7844    /**
7845     * Gets whether the TextView includes extra top and bottom padding to make
7846     * room for accents that go above the normal ascent and descent.
7847     *
7848     * @see #setIncludeFontPadding(boolean)
7849     *
7850     * @attr ref android.R.styleable#TextView_includeFontPadding
7851     */
7852    public boolean getIncludeFontPadding() {
7853        return mIncludePad;
7854    }
7855
7856    private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
7857
7858    @Override
7859    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
7860        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
7861        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
7862        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
7863        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
7864
7865        int width;
7866        int height;
7867
7868        BoringLayout.Metrics boring = UNKNOWN_BORING;
7869        BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
7870
7871        if (mTextDir == null) {
7872            mTextDir = getTextDirectionHeuristic();
7873        }
7874
7875        int des = -1;
7876        boolean fromexisting = false;
7877
7878        if (widthMode == MeasureSpec.EXACTLY) {
7879            // Parent has told us how big to be. So be it.
7880            width = widthSize;
7881        } else {
7882            if (mLayout != null && mEllipsize == null) {
7883                des = desired(mLayout);
7884            }
7885
7886            if (des < 0) {
7887                boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
7888                if (boring != null) {
7889                    mBoring = boring;
7890                }
7891            } else {
7892                fromexisting = true;
7893            }
7894
7895            if (boring == null || boring == UNKNOWN_BORING) {
7896                if (des < 0) {
7897                    des = (int) Math.ceil(Layout.getDesiredWidth(mTransformed, 0,
7898                            mTransformed.length(), mTextPaint, mTextDir));
7899                }
7900                width = des;
7901            } else {
7902                width = boring.width;
7903            }
7904
7905            final Drawables dr = mDrawables;
7906            if (dr != null) {
7907                width = Math.max(width, dr.mDrawableWidthTop);
7908                width = Math.max(width, dr.mDrawableWidthBottom);
7909            }
7910
7911            if (mHint != null) {
7912                int hintDes = -1;
7913                int hintWidth;
7914
7915                if (mHintLayout != null && mEllipsize == null) {
7916                    hintDes = desired(mHintLayout);
7917                }
7918
7919                if (hintDes < 0) {
7920                    hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
7921                    if (hintBoring != null) {
7922                        mHintBoring = hintBoring;
7923                    }
7924                }
7925
7926                if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
7927                    if (hintDes < 0) {
7928                        hintDes = (int) Math.ceil(Layout.getDesiredWidth(mHint, 0, mHint.length(),
7929                                mTextPaint, mTextDir));
7930                    }
7931                    hintWidth = hintDes;
7932                } else {
7933                    hintWidth = hintBoring.width;
7934                }
7935
7936                if (hintWidth > width) {
7937                    width = hintWidth;
7938                }
7939            }
7940
7941            width += getCompoundPaddingLeft() + getCompoundPaddingRight();
7942
7943            if (mMaxWidthMode == EMS) {
7944                width = Math.min(width, mMaxWidth * getLineHeight());
7945            } else {
7946                width = Math.min(width, mMaxWidth);
7947            }
7948
7949            if (mMinWidthMode == EMS) {
7950                width = Math.max(width, mMinWidth * getLineHeight());
7951            } else {
7952                width = Math.max(width, mMinWidth);
7953            }
7954
7955            // Check against our minimum width
7956            width = Math.max(width, getSuggestedMinimumWidth());
7957
7958            if (widthMode == MeasureSpec.AT_MOST) {
7959                width = Math.min(widthSize, width);
7960            }
7961        }
7962
7963        int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
7964        int unpaddedWidth = want;
7965
7966        if (mHorizontallyScrolling) want = VERY_WIDE;
7967
7968        int hintWant = want;
7969        int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
7970
7971        if (mLayout == null) {
7972            makeNewLayout(want, hintWant, boring, hintBoring,
7973                          width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
7974        } else {
7975            final boolean layoutChanged = (mLayout.getWidth() != want) || (hintWidth != hintWant)
7976                    || (mLayout.getEllipsizedWidth()
7977                            != width - getCompoundPaddingLeft() - getCompoundPaddingRight());
7978
7979            final boolean widthChanged = (mHint == null) && (mEllipsize == null)
7980                    && (want > mLayout.getWidth())
7981                    && (mLayout instanceof BoringLayout
7982                            || (fromexisting && des >= 0 && des <= want));
7983
7984            final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
7985
7986            if (layoutChanged || maximumChanged) {
7987                if (!maximumChanged && widthChanged) {
7988                    mLayout.increaseWidthTo(want);
7989                } else {
7990                    makeNewLayout(want, hintWant, boring, hintBoring,
7991                            width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
7992                }
7993            } else {
7994                // Nothing has changed
7995            }
7996        }
7997
7998        if (heightMode == MeasureSpec.EXACTLY) {
7999            // Parent has told us how big to be. So be it.
8000            height = heightSize;
8001            mDesiredHeightAtMeasure = -1;
8002        } else {
8003            int desired = getDesiredHeight();
8004
8005            height = desired;
8006            mDesiredHeightAtMeasure = desired;
8007
8008            if (heightMode == MeasureSpec.AT_MOST) {
8009                height = Math.min(desired, heightSize);
8010            }
8011        }
8012
8013        int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
8014        if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
8015            unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
8016        }
8017
8018        /*
8019         * We didn't let makeNewLayout() register to bring the cursor into view,
8020         * so do it here if there is any possibility that it is needed.
8021         */
8022        if (mMovement != null
8023                || mLayout.getWidth() > unpaddedWidth
8024                || mLayout.getHeight() > unpaddedHeight) {
8025            registerForPreDraw();
8026        } else {
8027            scrollTo(0, 0);
8028        }
8029
8030        setMeasuredDimension(width, height);
8031    }
8032
8033    /**
8034     * Automatically computes and sets the text size.
8035     */
8036    private void autoSizeText() {
8037        final int maxWidth = getWidth() - getTotalPaddingLeft() - getTotalPaddingRight();
8038        final int maxHeight = getHeight() - getExtendedPaddingBottom() - getExtendedPaddingTop();
8039
8040        if (maxWidth <= 0 || maxHeight <= 0) {
8041            return;
8042        }
8043
8044        synchronized (TEMP_RECTF) {
8045            TEMP_RECTF.setEmpty();
8046            TEMP_RECTF.right = maxWidth;
8047            TEMP_RECTF.bottom = maxHeight;
8048            final float optimalTextSize = findLargestTextSizeWhichFits(TEMP_RECTF);
8049            if (optimalTextSize != getTextSize()) {
8050                setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, optimalTextSize);
8051            }
8052        }
8053    }
8054
8055    /**
8056     * Performs a binary search to find the largest text size that will still fit within the size
8057     * available to this view.
8058     */
8059    private int findLargestTextSizeWhichFits(RectF availableSpace) {
8060        final int sizesCount = mAutoSizeTextSizesInPx.length;
8061        if (sizesCount == 0) {
8062            throw new IllegalStateException("No available text sizes to choose from.");
8063        }
8064
8065        int bestSizeIndex = 0;
8066        int lowIndex = bestSizeIndex + 1;
8067        int highIndex = sizesCount - 1;
8068        int sizeToTryIndex;
8069        while (lowIndex <= highIndex) {
8070            sizeToTryIndex = (lowIndex + highIndex) / 2;
8071            if (suggestedSizeFitsInSpace(mAutoSizeTextSizesInPx[sizeToTryIndex], availableSpace)) {
8072                bestSizeIndex = lowIndex;
8073                lowIndex = sizeToTryIndex + 1;
8074            } else {
8075                highIndex = sizeToTryIndex - 1;
8076                bestSizeIndex = highIndex;
8077            }
8078        }
8079
8080        return mAutoSizeTextSizesInPx[bestSizeIndex];
8081    }
8082
8083    private boolean suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace) {
8084        final CharSequence text = getText();
8085        final int maxLines = getMaxLines();
8086        if (mTempTextPaint == null) {
8087            mTempTextPaint = new TextPaint();
8088        } else {
8089            mTempTextPaint.reset();
8090        }
8091        mTempTextPaint.set(getPaint());
8092        mTempTextPaint.setTextSize(suggestedSizeInPx);
8093
8094        if ((mLayout instanceof BoringLayout) && BoringLayout.isBoring(
8095                text, mTempTextPaint, getTextDirectionHeuristic(), mBoring) != null) {
8096            return mTempTextPaint.getFontSpacing() <= availableSpace.bottom
8097                    && mTempTextPaint.measureText(text, 0, text.length()) <= availableSpace.right;
8098        } else {
8099            StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain(text, 0, text.length(),
8100                    mTempTextPaint,
8101                    getMeasuredWidth() - getTotalPaddingLeft() - getTotalPaddingRight());
8102            layoutBuilder.setAlignment(getLayoutAlignment());
8103            layoutBuilder.setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier());
8104            layoutBuilder.setIncludePad(true);
8105            StaticLayout layout = layoutBuilder.build();
8106
8107            // Lines overflow.
8108            if (maxLines != -1 && layout.getLineCount() > maxLines) {
8109                return false;
8110            }
8111
8112            // Height overflow.
8113            if (layout.getHeight() > availableSpace.bottom) {
8114                return false;
8115            }
8116        }
8117
8118        return true;
8119    }
8120
8121    private int getDesiredHeight() {
8122        return Math.max(
8123                getDesiredHeight(mLayout, true),
8124                getDesiredHeight(mHintLayout, mEllipsize != null));
8125    }
8126
8127    private int getDesiredHeight(Layout layout, boolean cap) {
8128        if (layout == null) {
8129            return 0;
8130        }
8131
8132        /*
8133        * Don't cap the hint to a certain number of lines.
8134        * (Do cap it, though, if we have a maximum pixel height.)
8135        */
8136        int desired = layout.getHeight(cap);
8137
8138        final Drawables dr = mDrawables;
8139        if (dr != null) {
8140            desired = Math.max(desired, dr.mDrawableHeightLeft);
8141            desired = Math.max(desired, dr.mDrawableHeightRight);
8142        }
8143
8144        int linecount = layout.getLineCount();
8145        final int padding = getCompoundPaddingTop() + getCompoundPaddingBottom();
8146        desired += padding;
8147
8148        if (mMaxMode != LINES) {
8149            desired = Math.min(desired, mMaximum);
8150        } else if (cap && linecount > mMaximum && layout instanceof DynamicLayout) {
8151            desired = layout.getLineTop(mMaximum);
8152
8153            if (dr != null) {
8154                desired = Math.max(desired, dr.mDrawableHeightLeft);
8155                desired = Math.max(desired, dr.mDrawableHeightRight);
8156            }
8157
8158            desired += padding;
8159            linecount = mMaximum;
8160        }
8161
8162        if (mMinMode == LINES) {
8163            if (linecount < mMinimum) {
8164                desired += getLineHeight() * (mMinimum - linecount);
8165            }
8166        } else {
8167            desired = Math.max(desired, mMinimum);
8168        }
8169
8170        // Check against our minimum height
8171        desired = Math.max(desired, getSuggestedMinimumHeight());
8172
8173        return desired;
8174    }
8175
8176    /**
8177     * Check whether a change to the existing text layout requires a
8178     * new view layout.
8179     */
8180    private void checkForResize() {
8181        boolean sizeChanged = false;
8182
8183        if (mLayout != null) {
8184            // Check if our width changed
8185            if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
8186                sizeChanged = true;
8187                invalidate();
8188            }
8189
8190            // Check if our height changed
8191            if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
8192                int desiredHeight = getDesiredHeight();
8193
8194                if (desiredHeight != this.getHeight()) {
8195                    sizeChanged = true;
8196                }
8197            } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
8198                if (mDesiredHeightAtMeasure >= 0) {
8199                    int desiredHeight = getDesiredHeight();
8200
8201                    if (desiredHeight != mDesiredHeightAtMeasure) {
8202                        sizeChanged = true;
8203                    }
8204                }
8205            }
8206        }
8207
8208        if (sizeChanged) {
8209            requestLayout();
8210            // caller will have already invalidated
8211        }
8212    }
8213
8214    /**
8215     * Check whether entirely new text requires a new view layout
8216     * or merely a new text layout.
8217     */
8218    private void checkForRelayout() {
8219        // If we have a fixed width, we can just swap in a new text layout
8220        // if the text height stays the same or if the view height is fixed.
8221
8222        if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT
8223                || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))
8224                && (mHint == null || mHintLayout != null)
8225                && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
8226            // Static width, so try making a new text layout.
8227
8228            int oldht = mLayout.getHeight();
8229            int want = mLayout.getWidth();
8230            int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
8231
8232            /*
8233             * No need to bring the text into view, since the size is not
8234             * changing (unless we do the requestLayout(), in which case it
8235             * will happen at measure).
8236             */
8237            makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
8238                          mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
8239                          false);
8240
8241            if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
8242                // In a fixed-height view, so use our new text layout.
8243                if (mLayoutParams.height != LayoutParams.WRAP_CONTENT
8244                        && mLayoutParams.height != LayoutParams.MATCH_PARENT) {
8245                    invalidate();
8246                    return;
8247                }
8248
8249                // Dynamic height, but height has stayed the same,
8250                // so use our new text layout.
8251                if (mLayout.getHeight() == oldht
8252                        && (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
8253                    invalidate();
8254                    return;
8255                }
8256            }
8257
8258            // We lose: the height has changed and we have a dynamic height.
8259            // Request a new view layout using our new text layout.
8260            requestLayout();
8261            invalidate();
8262        } else {
8263            // Dynamic width, so we have no choice but to request a new
8264            // view layout with a new text layout.
8265            nullLayouts();
8266            requestLayout();
8267            invalidate();
8268        }
8269    }
8270
8271    @Override
8272    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
8273        super.onLayout(changed, left, top, right, bottom);
8274        if (mDeferScroll >= 0) {
8275            int curs = mDeferScroll;
8276            mDeferScroll = -1;
8277            bringPointIntoView(Math.min(curs, mText.length()));
8278        }
8279
8280        if (isAutoSizeEnabled()) {
8281            if (mNeedsAutoSizeText) {
8282                // Call auto-size after the width and height have been calculated.
8283                autoSizeText();
8284            }
8285            // Always try to auto-size if enabled. Functions that do not want to trigger auto-sizing
8286            // after the next layout round should set this to false.
8287            mNeedsAutoSizeText = true;
8288        }
8289    }
8290
8291    private boolean isShowingHint() {
8292        return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint);
8293    }
8294
8295    /**
8296     * Returns true if anything changed.
8297     */
8298    private boolean bringTextIntoView() {
8299        Layout layout = isShowingHint() ? mHintLayout : mLayout;
8300        int line = 0;
8301        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
8302            line = layout.getLineCount() - 1;
8303        }
8304
8305        Layout.Alignment a = layout.getParagraphAlignment(line);
8306        int dir = layout.getParagraphDirection(line);
8307        int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
8308        int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
8309        int ht = layout.getHeight();
8310
8311        int scrollx, scrolly;
8312
8313        // Convert to left, center, or right alignment.
8314        if (a == Layout.Alignment.ALIGN_NORMAL) {
8315            a = dir == Layout.DIR_LEFT_TO_RIGHT
8316                    ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
8317        } else if (a == Layout.Alignment.ALIGN_OPPOSITE) {
8318            a = dir == Layout.DIR_LEFT_TO_RIGHT
8319                    ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
8320        }
8321
8322        if (a == Layout.Alignment.ALIGN_CENTER) {
8323            /*
8324             * Keep centered if possible, or, if it is too wide to fit,
8325             * keep leading edge in view.
8326             */
8327
8328            int left = (int) Math.floor(layout.getLineLeft(line));
8329            int right = (int) Math.ceil(layout.getLineRight(line));
8330
8331            if (right - left < hspace) {
8332                scrollx = (right + left) / 2 - hspace / 2;
8333            } else {
8334                if (dir < 0) {
8335                    scrollx = right - hspace;
8336                } else {
8337                    scrollx = left;
8338                }
8339            }
8340        } else if (a == Layout.Alignment.ALIGN_RIGHT) {
8341            int right = (int) Math.ceil(layout.getLineRight(line));
8342            scrollx = right - hspace;
8343        } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
8344            scrollx = (int) Math.floor(layout.getLineLeft(line));
8345        }
8346
8347        if (ht < vspace) {
8348            scrolly = 0;
8349        } else {
8350            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
8351                scrolly = ht - vspace;
8352            } else {
8353                scrolly = 0;
8354            }
8355        }
8356
8357        if (scrollx != mScrollX || scrolly != mScrollY) {
8358            scrollTo(scrollx, scrolly);
8359            return true;
8360        } else {
8361            return false;
8362        }
8363    }
8364
8365    /**
8366     * Move the point, specified by the offset, into the view if it is needed.
8367     * This has to be called after layout. Returns true if anything changed.
8368     */
8369    public boolean bringPointIntoView(int offset) {
8370        if (isLayoutRequested()) {
8371            mDeferScroll = offset;
8372            return false;
8373        }
8374        boolean changed = false;
8375
8376        Layout layout = isShowingHint() ? mHintLayout : mLayout;
8377
8378        if (layout == null) return changed;
8379
8380        int line = layout.getLineForOffset(offset);
8381
8382        int grav;
8383
8384        switch (layout.getParagraphAlignment(line)) {
8385            case ALIGN_LEFT:
8386                grav = 1;
8387                break;
8388            case ALIGN_RIGHT:
8389                grav = -1;
8390                break;
8391            case ALIGN_NORMAL:
8392                grav = layout.getParagraphDirection(line);
8393                break;
8394            case ALIGN_OPPOSITE:
8395                grav = -layout.getParagraphDirection(line);
8396                break;
8397            case ALIGN_CENTER:
8398            default:
8399                grav = 0;
8400                break;
8401        }
8402
8403        // We only want to clamp the cursor to fit within the layout width
8404        // in left-to-right modes, because in a right to left alignment,
8405        // we want to scroll to keep the line-right on the screen, as other
8406        // lines are likely to have text flush with the right margin, which
8407        // we want to keep visible.
8408        // A better long-term solution would probably be to measure both
8409        // the full line and a blank-trimmed version, and, for example, use
8410        // the latter measurement for centering and right alignment, but for
8411        // the time being we only implement the cursor clamping in left to
8412        // right where it is most likely to be annoying.
8413        final boolean clamped = grav > 0;
8414        // FIXME: Is it okay to truncate this, or should we round?
8415        final int x = (int) layout.getPrimaryHorizontal(offset, clamped, true);
8416        final int top = layout.getLineTop(line);
8417        final int bottom = layout.getLineTop(line + 1);
8418
8419        int left = (int) Math.floor(layout.getLineLeft(line));
8420        int right = (int) Math.ceil(layout.getLineRight(line));
8421        int ht = layout.getHeight();
8422
8423        int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
8424        int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
8425        if (!mHorizontallyScrolling && right - left > hspace && right > x) {
8426            // If cursor has been clamped, make sure we don't scroll.
8427            right = Math.max(x, left + hspace);
8428        }
8429
8430        int hslack = (bottom - top) / 2;
8431        int vslack = hslack;
8432
8433        if (vslack > vspace / 4) {
8434            vslack = vspace / 4;
8435        }
8436        if (hslack > hspace / 4) {
8437            hslack = hspace / 4;
8438        }
8439
8440        int hs = mScrollX;
8441        int vs = mScrollY;
8442
8443        if (top - vs < vslack) {
8444            vs = top - vslack;
8445        }
8446        if (bottom - vs > vspace - vslack) {
8447            vs = bottom - (vspace - vslack);
8448        }
8449        if (ht - vs < vspace) {
8450            vs = ht - vspace;
8451        }
8452        if (0 - vs > 0) {
8453            vs = 0;
8454        }
8455
8456        if (grav != 0) {
8457            if (x - hs < hslack) {
8458                hs = x - hslack;
8459            }
8460            if (x - hs > hspace - hslack) {
8461                hs = x - (hspace - hslack);
8462            }
8463        }
8464
8465        if (grav < 0) {
8466            if (left - hs > 0) {
8467                hs = left;
8468            }
8469            if (right - hs < hspace) {
8470                hs = right - hspace;
8471            }
8472        } else if (grav > 0) {
8473            if (right - hs < hspace) {
8474                hs = right - hspace;
8475            }
8476            if (left - hs > 0) {
8477                hs = left;
8478            }
8479        } else /* grav == 0 */ {
8480            if (right - left <= hspace) {
8481                /*
8482                 * If the entire text fits, center it exactly.
8483                 */
8484                hs = left - (hspace - (right - left)) / 2;
8485            } else if (x > right - hslack) {
8486                /*
8487                 * If we are near the right edge, keep the right edge
8488                 * at the edge of the view.
8489                 */
8490                hs = right - hspace;
8491            } else if (x < left + hslack) {
8492                /*
8493                 * If we are near the left edge, keep the left edge
8494                 * at the edge of the view.
8495                 */
8496                hs = left;
8497            } else if (left > hs) {
8498                /*
8499                 * Is there whitespace visible at the left?  Fix it if so.
8500                 */
8501                hs = left;
8502            } else if (right < hs + hspace) {
8503                /*
8504                 * Is there whitespace visible at the right?  Fix it if so.
8505                 */
8506                hs = right - hspace;
8507            } else {
8508                /*
8509                 * Otherwise, float as needed.
8510                 */
8511                if (x - hs < hslack) {
8512                    hs = x - hslack;
8513                }
8514                if (x - hs > hspace - hslack) {
8515                    hs = x - (hspace - hslack);
8516                }
8517            }
8518        }
8519
8520        if (hs != mScrollX || vs != mScrollY) {
8521            if (mScroller == null) {
8522                scrollTo(hs, vs);
8523            } else {
8524                long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
8525                int dx = hs - mScrollX;
8526                int dy = vs - mScrollY;
8527
8528                if (duration > ANIMATED_SCROLL_GAP) {
8529                    mScroller.startScroll(mScrollX, mScrollY, dx, dy);
8530                    awakenScrollBars(mScroller.getDuration());
8531                    invalidate();
8532                } else {
8533                    if (!mScroller.isFinished()) {
8534                        mScroller.abortAnimation();
8535                    }
8536
8537                    scrollBy(dx, dy);
8538                }
8539
8540                mLastScroll = AnimationUtils.currentAnimationTimeMillis();
8541            }
8542
8543            changed = true;
8544        }
8545
8546        if (isFocused()) {
8547            // This offsets because getInterestingRect() is in terms of viewport coordinates, but
8548            // requestRectangleOnScreen() is in terms of content coordinates.
8549
8550            // The offsets here are to ensure the rectangle we are using is
8551            // within our view bounds, in case the cursor is on the far left
8552            // or right.  If it isn't withing the bounds, then this request
8553            // will be ignored.
8554            if (mTempRect == null) mTempRect = new Rect();
8555            mTempRect.set(x - 2, top, x + 2, bottom);
8556            getInterestingRect(mTempRect, line);
8557            mTempRect.offset(mScrollX, mScrollY);
8558
8559            if (requestRectangleOnScreen(mTempRect)) {
8560                changed = true;
8561            }
8562        }
8563
8564        return changed;
8565    }
8566
8567    /**
8568     * Move the cursor, if needed, so that it is at an offset that is visible
8569     * to the user.  This will not move the cursor if it represents more than
8570     * one character (a selection range).  This will only work if the
8571     * TextView contains spannable text; otherwise it will do nothing.
8572     *
8573     * @return True if the cursor was actually moved, false otherwise.
8574     */
8575    public boolean moveCursorToVisibleOffset() {
8576        if (!(mText instanceof Spannable)) {
8577            return false;
8578        }
8579        int start = getSelectionStart();
8580        int end = getSelectionEnd();
8581        if (start != end) {
8582            return false;
8583        }
8584
8585        // First: make sure the line is visible on screen:
8586
8587        int line = mLayout.getLineForOffset(start);
8588
8589        final int top = mLayout.getLineTop(line);
8590        final int bottom = mLayout.getLineTop(line + 1);
8591        final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
8592        int vslack = (bottom - top) / 2;
8593        if (vslack > vspace / 4) {
8594            vslack = vspace / 4;
8595        }
8596        final int vs = mScrollY;
8597
8598        if (top < (vs + vslack)) {
8599            line = mLayout.getLineForVertical(vs + vslack + (bottom - top));
8600        } else if (bottom > (vspace + vs - vslack)) {
8601            line = mLayout.getLineForVertical(vspace + vs - vslack - (bottom - top));
8602        }
8603
8604        // Next: make sure the character is visible on screen:
8605
8606        final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
8607        final int hs = mScrollX;
8608        final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
8609        final int rightChar = mLayout.getOffsetForHorizontal(line, hspace + hs);
8610
8611        // line might contain bidirectional text
8612        final int lowChar = leftChar < rightChar ? leftChar : rightChar;
8613        final int highChar = leftChar > rightChar ? leftChar : rightChar;
8614
8615        int newStart = start;
8616        if (newStart < lowChar) {
8617            newStart = lowChar;
8618        } else if (newStart > highChar) {
8619            newStart = highChar;
8620        }
8621
8622        if (newStart != start) {
8623            Selection.setSelection((Spannable) mText, newStart);
8624            return true;
8625        }
8626
8627        return false;
8628    }
8629
8630    @Override
8631    public void computeScroll() {
8632        if (mScroller != null) {
8633            if (mScroller.computeScrollOffset()) {
8634                mScrollX = mScroller.getCurrX();
8635                mScrollY = mScroller.getCurrY();
8636                invalidateParentCaches();
8637                postInvalidate();  // So we draw again
8638            }
8639        }
8640    }
8641
8642    private void getInterestingRect(Rect r, int line) {
8643        convertFromViewportToContentCoordinates(r);
8644
8645        // Rectangle can can be expanded on first and last line to take
8646        // padding into account.
8647        // TODO Take left/right padding into account too?
8648        if (line == 0) r.top -= getExtendedPaddingTop();
8649        if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
8650    }
8651
8652    private void convertFromViewportToContentCoordinates(Rect r) {
8653        final int horizontalOffset = viewportToContentHorizontalOffset();
8654        r.left += horizontalOffset;
8655        r.right += horizontalOffset;
8656
8657        final int verticalOffset = viewportToContentVerticalOffset();
8658        r.top += verticalOffset;
8659        r.bottom += verticalOffset;
8660    }
8661
8662    int viewportToContentHorizontalOffset() {
8663        return getCompoundPaddingLeft() - mScrollX;
8664    }
8665
8666    int viewportToContentVerticalOffset() {
8667        int offset = getExtendedPaddingTop() - mScrollY;
8668        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8669            offset += getVerticalOffset(false);
8670        }
8671        return offset;
8672    }
8673
8674    @Override
8675    public void debug(int depth) {
8676        super.debug(depth);
8677
8678        String output = debugIndent(depth);
8679        output += "frame={" + mLeft + ", " + mTop + ", " + mRight
8680                + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
8681                + "} ";
8682
8683        if (mText != null) {
8684
8685            output += "mText=\"" + mText + "\" ";
8686            if (mLayout != null) {
8687                output += "mLayout width=" + mLayout.getWidth()
8688                        + " height=" + mLayout.getHeight();
8689            }
8690        } else {
8691            output += "mText=NULL";
8692        }
8693        Log.d(VIEW_LOG_TAG, output);
8694    }
8695
8696    /**
8697     * Convenience for {@link Selection#getSelectionStart}.
8698     */
8699    @ViewDebug.ExportedProperty(category = "text")
8700    public int getSelectionStart() {
8701        return Selection.getSelectionStart(getText());
8702    }
8703
8704    /**
8705     * Convenience for {@link Selection#getSelectionEnd}.
8706     */
8707    @ViewDebug.ExportedProperty(category = "text")
8708    public int getSelectionEnd() {
8709        return Selection.getSelectionEnd(getText());
8710    }
8711
8712    /**
8713     * Return true iff there is a selection inside this text view.
8714     */
8715    public boolean hasSelection() {
8716        final int selectionStart = getSelectionStart();
8717        final int selectionEnd = getSelectionEnd();
8718
8719        return selectionStart >= 0 && selectionStart != selectionEnd;
8720    }
8721
8722    String getSelectedText() {
8723        if (!hasSelection()) {
8724            return null;
8725        }
8726
8727        final int start = getSelectionStart();
8728        final int end = getSelectionEnd();
8729        return String.valueOf(
8730                start > end ? mText.subSequence(end, start) : mText.subSequence(start, end));
8731    }
8732
8733    /**
8734     * Sets the properties of this field (lines, horizontally scrolling,
8735     * transformation method) to be for a single-line input.
8736     *
8737     * @attr ref android.R.styleable#TextView_singleLine
8738     */
8739    public void setSingleLine() {
8740        setSingleLine(true);
8741    }
8742
8743    /**
8744     * Sets the properties of this field to transform input to ALL CAPS
8745     * display. This may use a "small caps" formatting if available.
8746     * This setting will be ignored if this field is editable or selectable.
8747     *
8748     * This call replaces the current transformation method. Disabling this
8749     * will not necessarily restore the previous behavior from before this
8750     * was enabled.
8751     *
8752     * @see #setTransformationMethod(TransformationMethod)
8753     * @attr ref android.R.styleable#TextView_textAllCaps
8754     */
8755    public void setAllCaps(boolean allCaps) {
8756        if (allCaps) {
8757            setTransformationMethod(new AllCapsTransformationMethod(getContext()));
8758        } else {
8759            setTransformationMethod(null);
8760        }
8761    }
8762
8763    /**
8764     * If true, sets the properties of this field (number of lines, horizontally scrolling,
8765     * transformation method) to be for a single-line input; if false, restores these to the default
8766     * conditions.
8767     *
8768     * Note that the default conditions are not necessarily those that were in effect prior this
8769     * method, and you may want to reset these properties to your custom values.
8770     *
8771     * @attr ref android.R.styleable#TextView_singleLine
8772     */
8773    @android.view.RemotableViewMethod
8774    public void setSingleLine(boolean singleLine) {
8775        // Could be used, but may break backward compatibility.
8776        // if (mSingleLine == singleLine) return;
8777        setInputTypeSingleLine(singleLine);
8778        applySingleLine(singleLine, true, true);
8779    }
8780
8781    /**
8782     * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
8783     * @param singleLine
8784     */
8785    private void setInputTypeSingleLine(boolean singleLine) {
8786        if (mEditor != null
8787                && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
8788                        == EditorInfo.TYPE_CLASS_TEXT) {
8789            if (singleLine) {
8790                mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
8791            } else {
8792                mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
8793            }
8794        }
8795    }
8796
8797    private void applySingleLine(boolean singleLine, boolean applyTransformation,
8798            boolean changeMaxLines) {
8799        mSingleLine = singleLine;
8800        if (singleLine) {
8801            setLines(1);
8802            setHorizontallyScrolling(true);
8803            if (applyTransformation) {
8804                setTransformationMethod(SingleLineTransformationMethod.getInstance());
8805            }
8806        } else {
8807            if (changeMaxLines) {
8808                setMaxLines(Integer.MAX_VALUE);
8809            }
8810            setHorizontallyScrolling(false);
8811            if (applyTransformation) {
8812                setTransformationMethod(null);
8813            }
8814        }
8815    }
8816
8817    /**
8818     * Causes words in the text that are longer than the view's width
8819     * to be ellipsized instead of broken in the middle.  You may also
8820     * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
8821     * to constrain the text to a single line.  Use <code>null</code>
8822     * to turn off ellipsizing.
8823     *
8824     * If {@link #setMaxLines} has been used to set two or more lines,
8825     * only {@link android.text.TextUtils.TruncateAt#END} and
8826     * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported
8827     * (other ellipsizing types will not do anything).
8828     *
8829     * @attr ref android.R.styleable#TextView_ellipsize
8830     */
8831    public void setEllipsize(TextUtils.TruncateAt where) {
8832        // TruncateAt is an enum. != comparison is ok between these singleton objects.
8833        if (mEllipsize != where) {
8834            mEllipsize = where;
8835
8836            if (mLayout != null) {
8837                nullLayouts();
8838                requestLayout();
8839                invalidate();
8840            }
8841        }
8842    }
8843
8844    /**
8845     * Sets how many times to repeat the marquee animation. Only applied if the
8846     * TextView has marquee enabled. Set to -1 to repeat indefinitely.
8847     *
8848     * @see #getMarqueeRepeatLimit()
8849     *
8850     * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
8851     */
8852    public void setMarqueeRepeatLimit(int marqueeLimit) {
8853        mMarqueeRepeatLimit = marqueeLimit;
8854    }
8855
8856    /**
8857     * Gets the number of times the marquee animation is repeated. Only meaningful if the
8858     * TextView has marquee enabled.
8859     *
8860     * @return the number of times the marquee animation is repeated. -1 if the animation
8861     * repeats indefinitely
8862     *
8863     * @see #setMarqueeRepeatLimit(int)
8864     *
8865     * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
8866     */
8867    public int getMarqueeRepeatLimit() {
8868        return mMarqueeRepeatLimit;
8869    }
8870
8871    /**
8872     * Returns where, if anywhere, words that are longer than the view
8873     * is wide should be ellipsized.
8874     */
8875    @ViewDebug.ExportedProperty
8876    public TextUtils.TruncateAt getEllipsize() {
8877        return mEllipsize;
8878    }
8879
8880    /**
8881     * Set the TextView so that when it takes focus, all the text is
8882     * selected.
8883     *
8884     * @attr ref android.R.styleable#TextView_selectAllOnFocus
8885     */
8886    @android.view.RemotableViewMethod
8887    public void setSelectAllOnFocus(boolean selectAllOnFocus) {
8888        createEditorIfNeeded();
8889        mEditor.mSelectAllOnFocus = selectAllOnFocus;
8890
8891        if (selectAllOnFocus && !(mText instanceof Spannable)) {
8892            setText(mText, BufferType.SPANNABLE);
8893        }
8894    }
8895
8896    /**
8897     * Set whether the cursor is visible. The default is true. Note that this property only
8898     * makes sense for editable TextView.
8899     *
8900     * @see #isCursorVisible()
8901     *
8902     * @attr ref android.R.styleable#TextView_cursorVisible
8903     */
8904    @android.view.RemotableViewMethod
8905    public void setCursorVisible(boolean visible) {
8906        if (visible && mEditor == null) return; // visible is the default value with no edit data
8907        createEditorIfNeeded();
8908        if (mEditor.mCursorVisible != visible) {
8909            mEditor.mCursorVisible = visible;
8910            invalidate();
8911
8912            mEditor.makeBlink();
8913
8914            // InsertionPointCursorController depends on mCursorVisible
8915            mEditor.prepareCursorControllers();
8916        }
8917    }
8918
8919    /**
8920     * @return whether or not the cursor is visible (assuming this TextView is editable)
8921     *
8922     * @see #setCursorVisible(boolean)
8923     *
8924     * @attr ref android.R.styleable#TextView_cursorVisible
8925     */
8926    public boolean isCursorVisible() {
8927        // true is the default value
8928        return mEditor == null ? true : mEditor.mCursorVisible;
8929    }
8930
8931    private boolean canMarquee() {
8932        int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
8933        return width > 0 && (mLayout.getLineWidth(0) > width
8934                || (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null
8935                        && mSavedMarqueeModeLayout.getLineWidth(0) > width));
8936    }
8937
8938    private void startMarquee() {
8939        // Do not ellipsize EditText
8940        if (getKeyListener() != null) return;
8941
8942        if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
8943            return;
8944        }
8945
8946        if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected())
8947                && getLineCount() == 1 && canMarquee()) {
8948
8949            if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
8950                mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
8951                final Layout tmp = mLayout;
8952                mLayout = mSavedMarqueeModeLayout;
8953                mSavedMarqueeModeLayout = tmp;
8954                setHorizontalFadingEdgeEnabled(true);
8955                requestLayout();
8956                invalidate();
8957            }
8958
8959            if (mMarquee == null) mMarquee = new Marquee(this);
8960            mMarquee.start(mMarqueeRepeatLimit);
8961        }
8962    }
8963
8964    private void stopMarquee() {
8965        if (mMarquee != null && !mMarquee.isStopped()) {
8966            mMarquee.stop();
8967        }
8968
8969        if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
8970            mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
8971            final Layout tmp = mSavedMarqueeModeLayout;
8972            mSavedMarqueeModeLayout = mLayout;
8973            mLayout = tmp;
8974            setHorizontalFadingEdgeEnabled(false);
8975            requestLayout();
8976            invalidate();
8977        }
8978    }
8979
8980    private void startStopMarquee(boolean start) {
8981        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
8982            if (start) {
8983                startMarquee();
8984            } else {
8985                stopMarquee();
8986            }
8987        }
8988    }
8989
8990    /**
8991     * This method is called when the text is changed, in case any subclasses
8992     * would like to know.
8993     *
8994     * Within <code>text</code>, the <code>lengthAfter</code> characters
8995     * beginning at <code>start</code> have just replaced old text that had
8996     * length <code>lengthBefore</code>. It is an error to attempt to make
8997     * changes to <code>text</code> from this callback.
8998     *
8999     * @param text The text the TextView is displaying
9000     * @param start The offset of the start of the range of the text that was
9001     * modified
9002     * @param lengthBefore The length of the former text that has been replaced
9003     * @param lengthAfter The length of the replacement modified text
9004     */
9005    protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
9006        // intentionally empty, template pattern method can be overridden by subclasses
9007    }
9008
9009    /**
9010     * This method is called when the selection has changed, in case any
9011     * subclasses would like to know.
9012     *
9013     * @param selStart The new selection start location.
9014     * @param selEnd The new selection end location.
9015     */
9016    protected void onSelectionChanged(int selStart, int selEnd) {
9017        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
9018    }
9019
9020    /**
9021     * Adds a TextWatcher to the list of those whose methods are called
9022     * whenever this TextView's text changes.
9023     * <p>
9024     * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
9025     * not called after {@link #setText} calls.  Now, doing {@link #setText}
9026     * if there are any text changed listeners forces the buffer type to
9027     * Editable if it would not otherwise be and does call this method.
9028     */
9029    public void addTextChangedListener(TextWatcher watcher) {
9030        if (mListeners == null) {
9031            mListeners = new ArrayList<TextWatcher>();
9032        }
9033
9034        mListeners.add(watcher);
9035    }
9036
9037    /**
9038     * Removes the specified TextWatcher from the list of those whose
9039     * methods are called
9040     * whenever this TextView's text changes.
9041     */
9042    public void removeTextChangedListener(TextWatcher watcher) {
9043        if (mListeners != null) {
9044            int i = mListeners.indexOf(watcher);
9045
9046            if (i >= 0) {
9047                mListeners.remove(i);
9048            }
9049        }
9050    }
9051
9052    private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
9053        if (mListeners != null) {
9054            final ArrayList<TextWatcher> list = mListeners;
9055            final int count = list.size();
9056            for (int i = 0; i < count; i++) {
9057                list.get(i).beforeTextChanged(text, start, before, after);
9058            }
9059        }
9060
9061        // The spans that are inside or intersect the modified region no longer make sense
9062        removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class);
9063        removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class);
9064    }
9065
9066    // Removes all spans that are inside or actually overlap the start..end range
9067    private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) {
9068        if (!(mText instanceof Editable)) return;
9069        Editable text = (Editable) mText;
9070
9071        T[] spans = text.getSpans(start, end, type);
9072        final int length = spans.length;
9073        for (int i = 0; i < length; i++) {
9074            final int spanStart = text.getSpanStart(spans[i]);
9075            final int spanEnd = text.getSpanEnd(spans[i]);
9076            if (spanEnd == start || spanStart == end) break;
9077            text.removeSpan(spans[i]);
9078        }
9079    }
9080
9081    void removeAdjacentSuggestionSpans(final int pos) {
9082        if (!(mText instanceof Editable)) return;
9083        final Editable text = (Editable) mText;
9084
9085        final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class);
9086        final int length = spans.length;
9087        for (int i = 0; i < length; i++) {
9088            final int spanStart = text.getSpanStart(spans[i]);
9089            final int spanEnd = text.getSpanEnd(spans[i]);
9090            if (spanEnd == pos || spanStart == pos) {
9091                if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) {
9092                    text.removeSpan(spans[i]);
9093                }
9094            }
9095        }
9096    }
9097
9098    /**
9099     * Not private so it can be called from an inner class without going
9100     * through a thunk.
9101     */
9102    void sendOnTextChanged(CharSequence text, int start, int before, int after) {
9103        if (mListeners != null) {
9104            final ArrayList<TextWatcher> list = mListeners;
9105            final int count = list.size();
9106            for (int i = 0; i < count; i++) {
9107                list.get(i).onTextChanged(text, start, before, after);
9108            }
9109        }
9110
9111        if (mEditor != null) mEditor.sendOnTextChanged(start, after);
9112    }
9113
9114    /**
9115     * Not private so it can be called from an inner class without going
9116     * through a thunk.
9117     */
9118    void sendAfterTextChanged(Editable text) {
9119        if (mListeners != null) {
9120            final ArrayList<TextWatcher> list = mListeners;
9121            final int count = list.size();
9122            for (int i = 0; i < count; i++) {
9123                list.get(i).afterTextChanged(text);
9124            }
9125        }
9126
9127        // Always notify AutoFillManager - it will return right away if autofill is disabled.
9128        notifyAutoFillManagerAfterTextChanged();
9129
9130        hideErrorIfUnchanged();
9131    }
9132
9133    private void notifyAutoFillManagerAfterTextChanged() {
9134        final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
9135        if (afm != null) {
9136            if (DEBUG_AUTOFILL) {
9137                Log.v(LOG_TAG, "sendAfterTextChanged(): notify AFM for text=" + mText);
9138            }
9139            afm.notifyValueChanged(TextView.this);
9140        }
9141    }
9142
9143    void updateAfterEdit() {
9144        invalidate();
9145        int curs = getSelectionStart();
9146
9147        if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
9148            registerForPreDraw();
9149        }
9150
9151        checkForResize();
9152
9153        if (curs >= 0) {
9154            mHighlightPathBogus = true;
9155            if (mEditor != null) mEditor.makeBlink();
9156            bringPointIntoView(curs);
9157        }
9158    }
9159
9160    /**
9161     * Not private so it can be called from an inner class without going
9162     * through a thunk.
9163     */
9164    void handleTextChanged(CharSequence buffer, int start, int before, int after) {
9165        sLastCutCopyOrTextChangedTime = 0;
9166
9167        final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
9168        if (ims == null || ims.mBatchEditNesting == 0) {
9169            updateAfterEdit();
9170        }
9171        if (ims != null) {
9172            ims.mContentChanged = true;
9173            if (ims.mChangedStart < 0) {
9174                ims.mChangedStart = start;
9175                ims.mChangedEnd = start + before;
9176            } else {
9177                ims.mChangedStart = Math.min(ims.mChangedStart, start);
9178                ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
9179            }
9180            ims.mChangedDelta += after - before;
9181        }
9182        resetErrorChangedFlag();
9183        sendOnTextChanged(buffer, start, before, after);
9184        onTextChanged(buffer, start, before, after);
9185    }
9186
9187    /**
9188     * Not private so it can be called from an inner class without going
9189     * through a thunk.
9190     */
9191    void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
9192        // XXX Make the start and end move together if this ends up
9193        // spending too much time invalidating.
9194
9195        boolean selChanged = false;
9196        int newSelStart = -1, newSelEnd = -1;
9197
9198        final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
9199
9200        if (what == Selection.SELECTION_END) {
9201            selChanged = true;
9202            newSelEnd = newStart;
9203
9204            if (oldStart >= 0 || newStart >= 0) {
9205                invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
9206                checkForResize();
9207                registerForPreDraw();
9208                if (mEditor != null) mEditor.makeBlink();
9209            }
9210        }
9211
9212        if (what == Selection.SELECTION_START) {
9213            selChanged = true;
9214            newSelStart = newStart;
9215
9216            if (oldStart >= 0 || newStart >= 0) {
9217                int end = Selection.getSelectionEnd(buf);
9218                invalidateCursor(end, oldStart, newStart);
9219            }
9220        }
9221
9222        if (selChanged) {
9223            mHighlightPathBogus = true;
9224            if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
9225
9226            if ((buf.getSpanFlags(what) & Spanned.SPAN_INTERMEDIATE) == 0) {
9227                if (newSelStart < 0) {
9228                    newSelStart = Selection.getSelectionStart(buf);
9229                }
9230                if (newSelEnd < 0) {
9231                    newSelEnd = Selection.getSelectionEnd(buf);
9232                }
9233
9234                if (mEditor != null) {
9235                    mEditor.refreshTextActionMode();
9236                    if (!hasSelection()
9237                            && mEditor.getTextActionMode() == null && hasTransientState()) {
9238                        // User generated selection has been removed.
9239                        setHasTransientState(false);
9240                    }
9241                }
9242                onSelectionChanged(newSelStart, newSelEnd);
9243            }
9244        }
9245
9246        if (what instanceof UpdateAppearance || what instanceof ParagraphStyle
9247                || what instanceof CharacterStyle) {
9248            if (ims == null || ims.mBatchEditNesting == 0) {
9249                invalidate();
9250                mHighlightPathBogus = true;
9251                checkForResize();
9252            } else {
9253                ims.mContentChanged = true;
9254            }
9255            if (mEditor != null) {
9256                if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
9257                if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
9258                mEditor.invalidateHandlesAndActionMode();
9259            }
9260        }
9261
9262        if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
9263            mHighlightPathBogus = true;
9264            if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
9265                ims.mSelectionModeChanged = true;
9266            }
9267
9268            if (Selection.getSelectionStart(buf) >= 0) {
9269                if (ims == null || ims.mBatchEditNesting == 0) {
9270                    invalidateCursor();
9271                } else {
9272                    ims.mCursorChanged = true;
9273                }
9274            }
9275        }
9276
9277        if (what instanceof ParcelableSpan) {
9278            // If this is a span that can be sent to a remote process,
9279            // the current extract editor would be interested in it.
9280            if (ims != null && ims.mExtractedTextRequest != null) {
9281                if (ims.mBatchEditNesting != 0) {
9282                    if (oldStart >= 0) {
9283                        if (ims.mChangedStart > oldStart) {
9284                            ims.mChangedStart = oldStart;
9285                        }
9286                        if (ims.mChangedStart > oldEnd) {
9287                            ims.mChangedStart = oldEnd;
9288                        }
9289                    }
9290                    if (newStart >= 0) {
9291                        if (ims.mChangedStart > newStart) {
9292                            ims.mChangedStart = newStart;
9293                        }
9294                        if (ims.mChangedStart > newEnd) {
9295                            ims.mChangedStart = newEnd;
9296                        }
9297                    }
9298                } else {
9299                    if (DEBUG_EXTRACT) {
9300                        Log.v(LOG_TAG, "Span change outside of batch: "
9301                                + oldStart + "-" + oldEnd + ","
9302                                + newStart + "-" + newEnd + " " + what);
9303                    }
9304                    ims.mContentChanged = true;
9305                }
9306            }
9307        }
9308
9309        if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0
9310                && what instanceof SpellCheckSpan) {
9311            mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
9312        }
9313    }
9314
9315    @Override
9316    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
9317        if (isTemporarilyDetached()) {
9318            // If we are temporarily in the detach state, then do nothing.
9319            super.onFocusChanged(focused, direction, previouslyFocusedRect);
9320            return;
9321        }
9322
9323        if (mEditor != null) mEditor.onFocusChanged(focused, direction);
9324
9325        if (focused) {
9326            if (mText instanceof Spannable) {
9327                Spannable sp = (Spannable) mText;
9328                MetaKeyKeyListener.resetMetaState(sp);
9329            }
9330        }
9331
9332        startStopMarquee(focused);
9333
9334        if (mTransformation != null) {
9335            mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
9336        }
9337
9338        super.onFocusChanged(focused, direction, previouslyFocusedRect);
9339    }
9340
9341    @Override
9342    public void onWindowFocusChanged(boolean hasWindowFocus) {
9343        super.onWindowFocusChanged(hasWindowFocus);
9344
9345        if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
9346
9347        startStopMarquee(hasWindowFocus);
9348    }
9349
9350    @Override
9351    protected void onVisibilityChanged(View changedView, int visibility) {
9352        super.onVisibilityChanged(changedView, visibility);
9353        if (mEditor != null && visibility != VISIBLE) {
9354            mEditor.hideCursorAndSpanControllers();
9355            stopTextActionMode();
9356        }
9357    }
9358
9359    /**
9360     * Use {@link BaseInputConnection#removeComposingSpans
9361     * BaseInputConnection.removeComposingSpans()} to remove any IME composing
9362     * state from this text view.
9363     */
9364    public void clearComposingText() {
9365        if (mText instanceof Spannable) {
9366            BaseInputConnection.removeComposingSpans((Spannable) mText);
9367        }
9368    }
9369
9370    @Override
9371    public void setSelected(boolean selected) {
9372        boolean wasSelected = isSelected();
9373
9374        super.setSelected(selected);
9375
9376        if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
9377            if (selected) {
9378                startMarquee();
9379            } else {
9380                stopMarquee();
9381            }
9382        }
9383    }
9384
9385    @Override
9386    public boolean onTouchEvent(MotionEvent event) {
9387        final int action = event.getActionMasked();
9388        if (mEditor != null) {
9389            mEditor.onTouchEvent(event);
9390
9391            if (mEditor.mSelectionModifierCursorController != null
9392                    && mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) {
9393                return true;
9394            }
9395        }
9396
9397        final boolean superResult = super.onTouchEvent(event);
9398
9399        /*
9400         * Don't handle the release after a long press, because it will move the selection away from
9401         * whatever the menu action was trying to affect. If the long press should have triggered an
9402         * insertion action mode, we can now actually show it.
9403         */
9404        if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
9405            mEditor.mDiscardNextActionUp = false;
9406
9407            if (mEditor.mIsInsertionActionModeStartPending) {
9408                mEditor.startInsertionActionMode();
9409                mEditor.mIsInsertionActionModeStartPending = false;
9410            }
9411            return superResult;
9412        }
9413
9414        final boolean touchIsFinished = (action == MotionEvent.ACTION_UP)
9415                && (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
9416
9417        if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
9418                && mText instanceof Spannable && mLayout != null) {
9419            boolean handled = false;
9420
9421            if (mMovement != null) {
9422                handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
9423            }
9424
9425            final boolean textIsSelectable = isTextSelectable();
9426            if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
9427                // The LinkMovementMethod which should handle taps on links has not been installed
9428                // on non editable text that support text selection.
9429                // We reproduce its behavior here to open links for these.
9430                ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
9431                    getSelectionEnd(), ClickableSpan.class);
9432
9433                if (links.length > 0) {
9434                    links[0].onClick(this);
9435                    handled = true;
9436                }
9437            }
9438
9439            if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
9440                // Show the IME, except when selecting in read-only text.
9441                final InputMethodManager imm = InputMethodManager.peekInstance();
9442                viewClicked(imm);
9443                if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null) {
9444                    imm.showSoftInput(this, 0);
9445                }
9446
9447                // The above condition ensures that the mEditor is not null
9448                mEditor.onTouchUpEvent(event);
9449
9450                handled = true;
9451            }
9452
9453            if (handled) {
9454                return true;
9455            }
9456        }
9457
9458        return superResult;
9459    }
9460
9461    @Override
9462    public boolean onGenericMotionEvent(MotionEvent event) {
9463        if (mMovement != null && mText instanceof Spannable && mLayout != null) {
9464            try {
9465                if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) {
9466                    return true;
9467                }
9468            } catch (AbstractMethodError ex) {
9469                // onGenericMotionEvent was added to the MovementMethod interface in API 12.
9470                // Ignore its absence in case third party applications implemented the
9471                // interface directly.
9472            }
9473        }
9474        return super.onGenericMotionEvent(event);
9475    }
9476
9477    @Override
9478    protected void onCreateContextMenu(ContextMenu menu) {
9479        if (mEditor != null) {
9480            mEditor.onCreateContextMenu(menu);
9481        }
9482    }
9483
9484    @Override
9485    public boolean showContextMenu() {
9486        if (mEditor != null) {
9487            mEditor.setContextMenuAnchor(Float.NaN, Float.NaN);
9488        }
9489        return super.showContextMenu();
9490    }
9491
9492    @Override
9493    public boolean showContextMenu(float x, float y) {
9494        if (mEditor != null) {
9495            mEditor.setContextMenuAnchor(x, y);
9496        }
9497        return super.showContextMenu(x, y);
9498    }
9499
9500    /**
9501     * @return True iff this TextView contains a text that can be edited, or if this is
9502     * a selectable TextView.
9503     */
9504    boolean isTextEditable() {
9505        return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
9506    }
9507
9508    /**
9509     * Returns true, only while processing a touch gesture, if the initial
9510     * touch down event caused focus to move to the text view and as a result
9511     * its selection changed.  Only valid while processing the touch gesture
9512     * of interest, in an editable text view.
9513     */
9514    public boolean didTouchFocusSelect() {
9515        return mEditor != null && mEditor.mTouchFocusSelected;
9516    }
9517
9518    @Override
9519    public void cancelLongPress() {
9520        super.cancelLongPress();
9521        if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
9522    }
9523
9524    @Override
9525    public boolean onTrackballEvent(MotionEvent event) {
9526        if (mMovement != null && mText instanceof Spannable && mLayout != null) {
9527            if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
9528                return true;
9529            }
9530        }
9531
9532        return super.onTrackballEvent(event);
9533    }
9534
9535    public void setScroller(Scroller s) {
9536        mScroller = s;
9537    }
9538
9539    @Override
9540    protected float getLeftFadingEdgeStrength() {
9541        if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
9542            final Marquee marquee = mMarquee;
9543            if (marquee.shouldDrawLeftFade()) {
9544                return getHorizontalFadingEdgeStrength(marquee.getScroll(), 0.0f);
9545            } else {
9546                return 0.0f;
9547            }
9548        } else if (getLineCount() == 1) {
9549            final float lineLeft = getLayout().getLineLeft(0);
9550            if (lineLeft > mScrollX) return 0.0f;
9551            return getHorizontalFadingEdgeStrength(mScrollX, lineLeft);
9552        }
9553        return super.getLeftFadingEdgeStrength();
9554    }
9555
9556    @Override
9557    protected float getRightFadingEdgeStrength() {
9558        if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
9559            final Marquee marquee = mMarquee;
9560            return getHorizontalFadingEdgeStrength(marquee.getMaxFadeScroll(), marquee.getScroll());
9561        } else if (getLineCount() == 1) {
9562            final float rightEdge = mScrollX +
9563                    (getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight());
9564            final float lineRight = getLayout().getLineRight(0);
9565            if (lineRight < rightEdge) return 0.0f;
9566            return getHorizontalFadingEdgeStrength(rightEdge, lineRight);
9567        }
9568        return super.getRightFadingEdgeStrength();
9569    }
9570
9571    /**
9572     * Calculates the fading edge strength as the ratio of the distance between two
9573     * horizontal positions to {@link View#getHorizontalFadingEdgeLength()}. Uses the absolute
9574     * value for the distance calculation.
9575     *
9576     * @param position1 A horizontal position.
9577     * @param position2 A horizontal position.
9578     * @return Fading edge strength between [0.0f, 1.0f].
9579     */
9580    @FloatRange(from = 0.0, to = 1.0)
9581    private float getHorizontalFadingEdgeStrength(float position1, float position2) {
9582        final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength();
9583        if (horizontalFadingEdgeLength == 0) return 0.0f;
9584        final float diff = Math.abs(position1 - position2);
9585        if (diff > horizontalFadingEdgeLength) return 1.0f;
9586        return diff / horizontalFadingEdgeLength;
9587    }
9588
9589    private boolean isMarqueeFadeEnabled() {
9590        return mEllipsize == TextUtils.TruncateAt.MARQUEE
9591                && mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
9592    }
9593
9594    @Override
9595    protected int computeHorizontalScrollRange() {
9596        if (mLayout != null) {
9597            return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT
9598                    ? (int) mLayout.getLineWidth(0) : mLayout.getWidth();
9599        }
9600
9601        return super.computeHorizontalScrollRange();
9602    }
9603
9604    @Override
9605    protected int computeVerticalScrollRange() {
9606        if (mLayout != null) {
9607            return mLayout.getHeight();
9608        }
9609        return super.computeVerticalScrollRange();
9610    }
9611
9612    @Override
9613    protected int computeVerticalScrollExtent() {
9614        return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
9615    }
9616
9617    @Override
9618    public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
9619        super.findViewsWithText(outViews, searched, flags);
9620        if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
9621                && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
9622            String searchedLowerCase = searched.toString().toLowerCase();
9623            String textLowerCase = mText.toString().toLowerCase();
9624            if (textLowerCase.contains(searchedLowerCase)) {
9625                outViews.add(this);
9626            }
9627        }
9628    }
9629
9630    /**
9631     * Type of the text buffer that defines the characteristics of the text such as static,
9632     * styleable, or editable.
9633     */
9634    public enum BufferType {
9635        NORMAL, SPANNABLE, EDITABLE
9636    }
9637
9638    /**
9639     * Returns the TextView_textColor attribute from the TypedArray, if set, or
9640     * the TextAppearance_textColor from the TextView_textAppearance attribute,
9641     * if TextView_textColor was not set directly.
9642     *
9643     * @removed
9644     */
9645    public static ColorStateList getTextColors(Context context, TypedArray attrs) {
9646        if (attrs == null) {
9647            // Preserve behavior prior to removal of this API.
9648            throw new NullPointerException();
9649        }
9650
9651        // It's not safe to use this method from apps. The parameter 'attrs'
9652        // must have been obtained using the TextView filter array which is not
9653        // available to the SDK. As such, we grab a default TypedArray with the
9654        // right filter instead here.
9655        final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView);
9656        ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor);
9657        if (colors == null) {
9658            final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0);
9659            if (ap != 0) {
9660                final TypedArray appearance = context.obtainStyledAttributes(
9661                        ap, R.styleable.TextAppearance);
9662                colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor);
9663                appearance.recycle();
9664            }
9665        }
9666        a.recycle();
9667
9668        return colors;
9669    }
9670
9671    /**
9672     * Returns the default color from the TextView_textColor attribute from the
9673     * AttributeSet, if set, or the default color from the
9674     * TextAppearance_textColor from the TextView_textAppearance attribute, if
9675     * TextView_textColor was not set directly.
9676     *
9677     * @removed
9678     */
9679    public static int getTextColor(Context context, TypedArray attrs, int def) {
9680        final ColorStateList colors = getTextColors(context, attrs);
9681        if (colors == null) {
9682            return def;
9683        } else {
9684            return colors.getDefaultColor();
9685        }
9686    }
9687
9688    @Override
9689    public boolean onKeyShortcut(int keyCode, KeyEvent event) {
9690        if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
9691            // Handle Ctrl-only shortcuts.
9692            switch (keyCode) {
9693                case KeyEvent.KEYCODE_A:
9694                    if (canSelectText()) {
9695                        return onTextContextMenuItem(ID_SELECT_ALL);
9696                    }
9697                    break;
9698                case KeyEvent.KEYCODE_Z:
9699                    if (canUndo()) {
9700                        return onTextContextMenuItem(ID_UNDO);
9701                    }
9702                    break;
9703                case KeyEvent.KEYCODE_X:
9704                    if (canCut()) {
9705                        return onTextContextMenuItem(ID_CUT);
9706                    }
9707                    break;
9708                case KeyEvent.KEYCODE_C:
9709                    if (canCopy()) {
9710                        return onTextContextMenuItem(ID_COPY);
9711                    }
9712                    break;
9713                case KeyEvent.KEYCODE_V:
9714                    if (canPaste()) {
9715                        return onTextContextMenuItem(ID_PASTE);
9716                    }
9717                    break;
9718            }
9719        } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
9720            // Handle Ctrl-Shift shortcuts.
9721            switch (keyCode) {
9722                case KeyEvent.KEYCODE_Z:
9723                    if (canRedo()) {
9724                        return onTextContextMenuItem(ID_REDO);
9725                    }
9726                    break;
9727                case KeyEvent.KEYCODE_V:
9728                    if (canPaste()) {
9729                        return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT);
9730                    }
9731            }
9732        }
9733        return super.onKeyShortcut(keyCode, event);
9734    }
9735
9736    /**
9737     * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
9738     * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
9739     * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
9740     * sufficient.
9741     */
9742    boolean canSelectText() {
9743        return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
9744    }
9745
9746    /**
9747     * Test based on the <i>intrinsic</i> charateristics of the TextView.
9748     * The text must be spannable and the movement method must allow for arbitary selection.
9749     *
9750     * See also {@link #canSelectText()}.
9751     */
9752    boolean textCanBeSelected() {
9753        // prepareCursorController() relies on this method.
9754        // If you change this condition, make sure prepareCursorController is called anywhere
9755        // the value of this condition might be changed.
9756        if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
9757        return isTextEditable()
9758                || (isTextSelectable() && mText instanceof Spannable && isEnabled());
9759    }
9760
9761    private Locale getTextServicesLocale(boolean allowNullLocale) {
9762        // Start fetching the text services locale asynchronously.
9763        updateTextServicesLocaleAsync();
9764        // If !allowNullLocale and there is no cached text services locale, just return the default
9765        // locale.
9766        return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
9767                : mCurrentSpellCheckerLocaleCache;
9768    }
9769
9770    /**
9771     * This is a temporary method. Future versions may support multi-locale text.
9772     * Caveat: This method may not return the latest text services locale, but this should be
9773     * acceptable and it's more important to make this method asynchronous.
9774     *
9775     * @return The locale that should be used for a word iterator
9776     * in this TextView, based on the current spell checker settings,
9777     * the current IME's locale, or the system default locale.
9778     * Please note that a word iterator in this TextView is different from another word iterator
9779     * used by SpellChecker.java of TextView. This method should be used for the former.
9780     * @hide
9781     */
9782    // TODO: Support multi-locale
9783    // TODO: Update the text services locale immediately after the keyboard locale is switched
9784    // by catching intent of keyboard switch event
9785    public Locale getTextServicesLocale() {
9786        return getTextServicesLocale(false /* allowNullLocale */);
9787    }
9788
9789    /**
9790     * @return {@code true} if this TextView is specialized for showing and interacting with the
9791     * extracted text in a full-screen input method.
9792     * @hide
9793     */
9794    public boolean isInExtractedMode() {
9795        return false;
9796    }
9797
9798    /**
9799     * @return {@code true} if this widget supports auto-sizing text and has been configured to
9800     * auto-size.
9801     */
9802    private boolean isAutoSizeEnabled() {
9803        return supportsAutoSizeText() && mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_NONE;
9804    }
9805
9806    /**
9807     * @return {@code true} if this TextView supports auto-sizing text to fit within its container.
9808     * @hide
9809     */
9810    protected boolean supportsAutoSizeText() {
9811        return true;
9812    }
9813
9814    /**
9815     * This is a temporary method. Future versions may support multi-locale text.
9816     * Caveat: This method may not return the latest spell checker locale, but this should be
9817     * acceptable and it's more important to make this method asynchronous.
9818     *
9819     * @return The locale that should be used for a spell checker in this TextView,
9820     * based on the current spell checker settings, the current IME's locale, or the system default
9821     * locale.
9822     * @hide
9823     */
9824    public Locale getSpellCheckerLocale() {
9825        return getTextServicesLocale(true /* allowNullLocale */);
9826    }
9827
9828    private void updateTextServicesLocaleAsync() {
9829        // AsyncTask.execute() uses a serial executor which means we don't have
9830        // to lock around updateTextServicesLocaleLocked() to prevent it from
9831        // being executed n times in parallel.
9832        AsyncTask.execute(new Runnable() {
9833            @Override
9834            public void run() {
9835                updateTextServicesLocaleLocked();
9836            }
9837        });
9838    }
9839
9840    private void updateTextServicesLocaleLocked() {
9841        final TextServicesManager textServicesManager = (TextServicesManager)
9842                mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
9843        final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
9844        final Locale locale;
9845        if (subtype != null) {
9846            locale = subtype.getLocaleObject();
9847        } else {
9848            locale = null;
9849        }
9850        mCurrentSpellCheckerLocaleCache = locale;
9851    }
9852
9853    void onLocaleChanged() {
9854        mEditor.onLocaleChanged();
9855    }
9856
9857    /**
9858     * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
9859     * Made available to achieve a consistent behavior.
9860     * @hide
9861     */
9862    public WordIterator getWordIterator() {
9863        if (mEditor != null) {
9864            return mEditor.getWordIterator();
9865        } else {
9866            return null;
9867        }
9868    }
9869
9870    /** @hide */
9871    @Override
9872    public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
9873        super.onPopulateAccessibilityEventInternal(event);
9874
9875        final CharSequence text = getTextForAccessibility();
9876        if (!TextUtils.isEmpty(text)) {
9877            event.getText().add(text);
9878        }
9879    }
9880
9881    @Override
9882    public CharSequence getAccessibilityClassName() {
9883        return TextView.class.getName();
9884    }
9885
9886    @Override
9887    public void onProvideStructure(ViewStructure structure) {
9888        super.onProvideStructure(structure);
9889        onProvideAutoStructureForAssistOrAutofill(structure, false);
9890    }
9891
9892    @Override
9893    public void onProvideAutofillStructure(ViewStructure structure, int flags) {
9894        super.onProvideAutofillStructure(structure, flags);
9895        onProvideAutoStructureForAssistOrAutofill(structure, true);
9896    }
9897
9898    private void onProvideAutoStructureForAssistOrAutofill(ViewStructure structure,
9899            boolean forAutofill) {
9900        final boolean isPassword = hasPasswordTransformationMethod()
9901                || isPasswordInputType(getInputType());
9902        if (forAutofill) {
9903            structure.setDataIsSensitive(!mTextFromResource);
9904        }
9905
9906        if (!isPassword || forAutofill) {
9907            if (mLayout == null) {
9908                assumeLayout();
9909            }
9910            Layout layout = mLayout;
9911            final int lineCount = layout.getLineCount();
9912            if (lineCount <= 1) {
9913                // Simple case: this is a single line.
9914                final CharSequence text = getText();
9915                structure.setText(text, getSelectionStart(), getSelectionEnd());
9916            } else {
9917                // Complex case: multi-line, could be scrolled or within a scroll container
9918                // so some lines are not visible.
9919                final int[] tmpCords = new int[2];
9920                getLocationInWindow(tmpCords);
9921                final int topWindowLocation = tmpCords[1];
9922                View root = this;
9923                ViewParent viewParent = getParent();
9924                while (viewParent instanceof View) {
9925                    root = (View) viewParent;
9926                    viewParent = root.getParent();
9927                }
9928                final int windowHeight = root.getHeight();
9929                final int topLine;
9930                final int bottomLine;
9931                if (topWindowLocation >= 0) {
9932                    // The top of the view is fully within its window; start text at line 0.
9933                    topLine = getLineAtCoordinateUnclamped(0);
9934                    bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1);
9935                } else {
9936                    // The top of hte window has scrolled off the top of the window; figure out
9937                    // the starting line for this.
9938                    topLine = getLineAtCoordinateUnclamped(-topWindowLocation);
9939                    bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1 - topWindowLocation);
9940                }
9941                // We want to return some contextual lines above/below the lines that are
9942                // actually visible.
9943                int expandedTopLine = topLine - (bottomLine - topLine) / 2;
9944                if (expandedTopLine < 0) {
9945                    expandedTopLine = 0;
9946                }
9947                int expandedBottomLine = bottomLine + (bottomLine - topLine) / 2;
9948                if (expandedBottomLine >= lineCount) {
9949                    expandedBottomLine = lineCount - 1;
9950                }
9951                // Convert lines into character offsets.
9952                int expandedTopChar = layout.getLineStart(expandedTopLine);
9953                int expandedBottomChar = layout.getLineEnd(expandedBottomLine);
9954                // Take into account selection -- if there is a selection, we need to expand
9955                // the text we are returning to include that selection.
9956                final int selStart = getSelectionStart();
9957                final int selEnd = getSelectionEnd();
9958                if (selStart < selEnd) {
9959                    if (selStart < expandedTopChar) {
9960                        expandedTopChar = selStart;
9961                    }
9962                    if (selEnd > expandedBottomChar) {
9963                        expandedBottomChar = selEnd;
9964                    }
9965                }
9966                // Get the text and trim it to the range we are reporting.
9967                CharSequence text = getText();
9968                if (expandedTopChar > 0 || expandedBottomChar < text.length()) {
9969                    text = text.subSequence(expandedTopChar, expandedBottomChar);
9970                }
9971                structure.setText(text, selStart - expandedTopChar, selEnd - expandedTopChar);
9972                final int[] lineOffsets = new int[bottomLine - topLine + 1];
9973                final int[] lineBaselines = new int[bottomLine - topLine + 1];
9974                final int baselineOffset = getBaselineOffset();
9975                for (int i = topLine; i <= bottomLine; i++) {
9976                    lineOffsets[i - topLine] = layout.getLineStart(i);
9977                    lineBaselines[i - topLine] = layout.getLineBaseline(i) + baselineOffset;
9978                }
9979                structure.setTextLines(lineOffsets, lineBaselines);
9980            }
9981
9982            // Extract style information that applies to the TextView as a whole.
9983            int style = 0;
9984            int typefaceStyle = getTypefaceStyle();
9985            if ((typefaceStyle & Typeface.BOLD) != 0) {
9986                style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
9987            }
9988            if ((typefaceStyle & Typeface.ITALIC) != 0) {
9989                style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC;
9990            }
9991
9992            // Global styles can also be set via TextView.setPaintFlags().
9993            int paintFlags = mTextPaint.getFlags();
9994            if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) {
9995                style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
9996            }
9997            if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) {
9998                style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE;
9999            }
10000            if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) {
10001                style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU;
10002            }
10003
10004            // TextView does not have its own text background color. A background is either part
10005            // of the View (and can be any drawable) or a BackgroundColorSpan inside the text.
10006            structure.setTextStyle(getTextSize(), getCurrentTextColor(),
10007                    AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style);
10008        }
10009        structure.setHint(getHint());
10010        structure.setInputType(getInputType());
10011    }
10012
10013    // TODO(b/33197203): add unit/CTS tests for autofill methods
10014
10015    boolean canRequestAutofill() {
10016        final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
10017        if (afm != null) {
10018            return afm.isEnabled();
10019        }
10020        return false;
10021    }
10022
10023    private void requestAutofill() {
10024        final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
10025        if (afm != null) {
10026            afm.requestAutofill(this);
10027        }
10028    }
10029
10030    @Override
10031    public boolean autofill(AutofillValue value) {
10032        if (value.isText()) {
10033            if (isTextEditable()) {
10034                setText(value.getTextValue(), mBufferType, true, 0);
10035                return true;
10036            }
10037        } else {
10038            Log.w(LOG_TAG, value + " could not be autofilled into " + this);
10039        }
10040
10041        return false;
10042    }
10043
10044    @Override
10045    public @AutofillType int getAutofillType() {
10046        return isTextEditable() ? AUTOFILL_TYPE_TEXT : AUTOFILL_TYPE_NONE;
10047    }
10048
10049    @Override
10050    @Nullable
10051    public AutofillValue getAutofillValue() {
10052        return isTextEditable() ? AutofillValue.forText(getText()) : null;
10053    }
10054
10055    /** @hide */
10056    @Override
10057    public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
10058        super.onInitializeAccessibilityEventInternal(event);
10059
10060        final boolean isPassword = hasPasswordTransformationMethod();
10061        event.setPassword(isPassword);
10062
10063        if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
10064            event.setFromIndex(Selection.getSelectionStart(mText));
10065            event.setToIndex(Selection.getSelectionEnd(mText));
10066            event.setItemCount(mText.length());
10067        }
10068    }
10069
10070    /** @hide */
10071    @Override
10072    public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
10073        super.onInitializeAccessibilityNodeInfoInternal(info);
10074
10075        final boolean isPassword = hasPasswordTransformationMethod();
10076        info.setPassword(isPassword);
10077        info.setText(getTextForAccessibility());
10078        info.setHintText(mHint);
10079        info.setShowingHintText(isShowingHint());
10080
10081        if (mBufferType == BufferType.EDITABLE) {
10082            info.setEditable(true);
10083            if (isEnabled()) {
10084                info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT);
10085            }
10086        }
10087
10088        if (mEditor != null) {
10089            info.setInputType(mEditor.mInputType);
10090
10091            if (mEditor.mError != null) {
10092                info.setContentInvalid(true);
10093                info.setError(mEditor.mError);
10094            }
10095        }
10096
10097        if (!TextUtils.isEmpty(mText)) {
10098            info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
10099            info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
10100            info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
10101                    | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
10102                    | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
10103                    | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
10104                    | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
10105            info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
10106            info.setAvailableExtraData(
10107                    Arrays.asList(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY));
10108        }
10109
10110        if (isFocused()) {
10111            if (canCopy()) {
10112                info.addAction(AccessibilityNodeInfo.ACTION_COPY);
10113            }
10114            if (canPaste()) {
10115                info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
10116            }
10117            if (canCut()) {
10118                info.addAction(AccessibilityNodeInfo.ACTION_CUT);
10119            }
10120            if (canShare()) {
10121                info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
10122                        ACCESSIBILITY_ACTION_SHARE,
10123                        getResources().getString(com.android.internal.R.string.share)));
10124            }
10125            if (canProcessText()) {  // also implies mEditor is not null.
10126                mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info);
10127            }
10128        }
10129
10130        // Check for known input filter types.
10131        final int numFilters = mFilters.length;
10132        for (int i = 0; i < numFilters; i++) {
10133            final InputFilter filter = mFilters[i];
10134            if (filter instanceof InputFilter.LengthFilter) {
10135                info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax());
10136            }
10137        }
10138
10139        if (!isSingleLine()) {
10140            info.setMultiLine(true);
10141        }
10142    }
10143
10144    @Override
10145    public void addExtraDataToAccessibilityNodeInfo(
10146            AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) {
10147        // The only extra data we support requires arguments.
10148        if (arguments == null) {
10149            return;
10150        }
10151        if (extraDataKey.equals(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)) {
10152            int positionInfoStartIndex = arguments.getInt(
10153                    EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, -1);
10154            int positionInfoLength = arguments.getInt(
10155                    EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH, -1);
10156            if ((positionInfoLength <= 0) || (positionInfoStartIndex < 0)
10157                    || (positionInfoStartIndex >= mText.length())) {
10158                Log.e(LOG_TAG, "Invalid arguments for accessibility character locations");
10159                return;
10160            }
10161            RectF[] boundingRects = new RectF[positionInfoLength];
10162            final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
10163            populateCharacterBounds(builder, positionInfoStartIndex,
10164                    positionInfoStartIndex + positionInfoLength,
10165                    viewportToContentHorizontalOffset(), viewportToContentVerticalOffset());
10166            CursorAnchorInfo cursorAnchorInfo = builder.setMatrix(null).build();
10167            if (mTempRect == null) mTempRect = new Rect();
10168            Rect viewBoundsInScreen = mTempRect;
10169            info.getBoundsInScreen(viewBoundsInScreen);
10170            for (int i = 0; i < positionInfoLength; i++) {
10171                int flags = cursorAnchorInfo.getCharacterBoundsFlags(positionInfoStartIndex + i);
10172                if ((flags & FLAG_HAS_VISIBLE_REGION) == FLAG_HAS_VISIBLE_REGION) {
10173                    RectF bounds = cursorAnchorInfo
10174                            .getCharacterBounds(positionInfoStartIndex + i);
10175                    if (bounds != null) {
10176                        bounds.offset(viewBoundsInScreen.left, viewBoundsInScreen.top);
10177                        boundingRects[i] = bounds;
10178                    }
10179                }
10180            }
10181            info.getExtras().putParcelableArray(extraDataKey, boundingRects);
10182        }
10183    }
10184
10185    /**
10186     * Populate requested character bounds in a {@link CursorAnchorInfo.Builder}
10187     *
10188     * @param builder The builder to populate
10189     * @param startIndex The starting character index to populate
10190     * @param endIndex The ending character index to populate
10191     * @param viewportToContentHorizontalOffset The horizontal offset from the viewport to the
10192     * content
10193     * @param viewportToContentVerticalOffset The vertical offset from the viewport to the content
10194     * @hide
10195     */
10196    public void populateCharacterBounds(CursorAnchorInfo.Builder builder,
10197            int startIndex, int endIndex, float viewportToContentHorizontalOffset,
10198            float viewportToContentVerticalOffset) {
10199        final int minLine = mLayout.getLineForOffset(startIndex);
10200        final int maxLine = mLayout.getLineForOffset(endIndex - 1);
10201        for (int line = minLine; line <= maxLine; ++line) {
10202            final int lineStart = mLayout.getLineStart(line);
10203            final int lineEnd = mLayout.getLineEnd(line);
10204            final int offsetStart = Math.max(lineStart, startIndex);
10205            final int offsetEnd = Math.min(lineEnd, endIndex);
10206            final boolean ltrLine =
10207                    mLayout.getParagraphDirection(line) == Layout.DIR_LEFT_TO_RIGHT;
10208            final float[] widths = new float[offsetEnd - offsetStart];
10209            mLayout.getPaint().getTextWidths(mText, offsetStart, offsetEnd, widths);
10210            final float top = mLayout.getLineTop(line);
10211            final float bottom = mLayout.getLineBottom(line);
10212            for (int offset = offsetStart; offset < offsetEnd; ++offset) {
10213                final float charWidth = widths[offset - offsetStart];
10214                final boolean isRtl = mLayout.isRtlCharAt(offset);
10215                final float primary = mLayout.getPrimaryHorizontal(offset);
10216                final float secondary = mLayout.getSecondaryHorizontal(offset);
10217                // TODO: This doesn't work perfectly for text with custom styles and
10218                // TAB chars.
10219                final float left;
10220                final float right;
10221                if (ltrLine) {
10222                    if (isRtl) {
10223                        left = secondary - charWidth;
10224                        right = secondary;
10225                    } else {
10226                        left = primary;
10227                        right = primary + charWidth;
10228                    }
10229                } else {
10230                    if (!isRtl) {
10231                        left = secondary;
10232                        right = secondary + charWidth;
10233                    } else {
10234                        left = primary - charWidth;
10235                        right = primary;
10236                    }
10237                }
10238                // TODO: Check top-right and bottom-left as well.
10239                final float localLeft = left + viewportToContentHorizontalOffset;
10240                final float localRight = right + viewportToContentHorizontalOffset;
10241                final float localTop = top + viewportToContentVerticalOffset;
10242                final float localBottom = bottom + viewportToContentVerticalOffset;
10243                final boolean isTopLeftVisible = isPositionVisible(localLeft, localTop);
10244                final boolean isBottomRightVisible =
10245                        isPositionVisible(localRight, localBottom);
10246                int characterBoundsFlags = 0;
10247                if (isTopLeftVisible || isBottomRightVisible) {
10248                    characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION;
10249                }
10250                if (!isTopLeftVisible || !isBottomRightVisible) {
10251                    characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
10252                }
10253                if (isRtl) {
10254                    characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL;
10255                }
10256                // Here offset is the index in Java chars.
10257                builder.addCharacterBounds(offset, localLeft, localTop, localRight,
10258                        localBottom, characterBoundsFlags);
10259            }
10260        }
10261    }
10262
10263    /**
10264     * @hide
10265     */
10266    public boolean isPositionVisible(final float positionX, final float positionY) {
10267        synchronized (TEMP_POSITION) {
10268            final float[] position = TEMP_POSITION;
10269            position[0] = positionX;
10270            position[1] = positionY;
10271            View view = this;
10272
10273            while (view != null) {
10274                if (view != this) {
10275                    // Local scroll is already taken into account in positionX/Y
10276                    position[0] -= view.getScrollX();
10277                    position[1] -= view.getScrollY();
10278                }
10279
10280                if (position[0] < 0 || position[1] < 0 || position[0] > view.getWidth()
10281                        || position[1] > view.getHeight()) {
10282                    return false;
10283                }
10284
10285                if (!view.getMatrix().isIdentity()) {
10286                    view.getMatrix().mapPoints(position);
10287                }
10288
10289                position[0] += view.getLeft();
10290                position[1] += view.getTop();
10291
10292                final ViewParent parent = view.getParent();
10293                if (parent instanceof View) {
10294                    view = (View) parent;
10295                } else {
10296                    // We've reached the ViewRoot, stop iterating
10297                    view = null;
10298                }
10299            }
10300        }
10301
10302        // We've been able to walk up the view hierarchy and the position was never clipped
10303        return true;
10304    }
10305
10306    /**
10307     * Performs an accessibility action after it has been offered to the
10308     * delegate.
10309     *
10310     * @hide
10311     */
10312    @Override
10313    public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
10314        if (mEditor != null
10315                && mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)) {
10316            return true;
10317        }
10318        switch (action) {
10319            case AccessibilityNodeInfo.ACTION_CLICK: {
10320                return performAccessibilityActionClick(arguments);
10321            }
10322            case AccessibilityNodeInfo.ACTION_COPY: {
10323                if (isFocused() && canCopy()) {
10324                    if (onTextContextMenuItem(ID_COPY)) {
10325                        return true;
10326                    }
10327                }
10328            } return false;
10329            case AccessibilityNodeInfo.ACTION_PASTE: {
10330                if (isFocused() && canPaste()) {
10331                    if (onTextContextMenuItem(ID_PASTE)) {
10332                        return true;
10333                    }
10334                }
10335            } return false;
10336            case AccessibilityNodeInfo.ACTION_CUT: {
10337                if (isFocused() && canCut()) {
10338                    if (onTextContextMenuItem(ID_CUT)) {
10339                        return true;
10340                    }
10341                }
10342            } return false;
10343            case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
10344                ensureIterableTextForAccessibilitySelectable();
10345                CharSequence text = getIterableTextForAccessibility();
10346                if (text == null) {
10347                    return false;
10348                }
10349                final int start = (arguments != null) ? arguments.getInt(
10350                        AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
10351                final int end = (arguments != null) ? arguments.getInt(
10352                        AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
10353                if ((getSelectionStart() != start || getSelectionEnd() != end)) {
10354                    // No arguments clears the selection.
10355                    if (start == end && end == -1) {
10356                        Selection.removeSelection((Spannable) text);
10357                        return true;
10358                    }
10359                    if (start >= 0 && start <= end && end <= text.length()) {
10360                        Selection.setSelection((Spannable) text, start, end);
10361                        // Make sure selection mode is engaged.
10362                        if (mEditor != null) {
10363                            mEditor.startSelectionActionMode();
10364                        }
10365                        return true;
10366                    }
10367                }
10368            } return false;
10369            case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
10370            case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
10371                ensureIterableTextForAccessibilitySelectable();
10372                return super.performAccessibilityActionInternal(action, arguments);
10373            }
10374            case ACCESSIBILITY_ACTION_SHARE: {
10375                if (isFocused() && canShare()) {
10376                    if (onTextContextMenuItem(ID_SHARE)) {
10377                        return true;
10378                    }
10379                }
10380            } return false;
10381            case AccessibilityNodeInfo.ACTION_SET_TEXT: {
10382                if (!isEnabled() || (mBufferType != BufferType.EDITABLE)) {
10383                    return false;
10384                }
10385                CharSequence text = (arguments != null) ? arguments.getCharSequence(
10386                        AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null;
10387                setText(text);
10388                if (mText != null) {
10389                    int updatedTextLength = mText.length();
10390                    if (updatedTextLength > 0) {
10391                        Selection.setSelection((Spannable) mText, updatedTextLength);
10392                    }
10393                }
10394            } return true;
10395            default: {
10396                return super.performAccessibilityActionInternal(action, arguments);
10397            }
10398        }
10399    }
10400
10401    private boolean performAccessibilityActionClick(Bundle arguments) {
10402        boolean handled = false;
10403
10404        if (!isEnabled()) {
10405            return false;
10406        }
10407
10408        if (isClickable() || isLongClickable()) {
10409            // Simulate View.onTouchEvent for an ACTION_UP event
10410            if (isFocusable() && !isFocused()) {
10411                requestFocus();
10412            }
10413
10414            performClick();
10415            handled = true;
10416        }
10417
10418        // Show the IME, except when selecting in read-only text.
10419        if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null
10420                && (isTextEditable() || isTextSelectable()) && isFocused()) {
10421            final InputMethodManager imm = InputMethodManager.peekInstance();
10422            viewClicked(imm);
10423            if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) {
10424                handled |= imm.showSoftInput(this, 0);
10425            }
10426        }
10427
10428        return handled;
10429    }
10430
10431    private boolean hasSpannableText() {
10432        return mText != null && mText instanceof Spannable;
10433    }
10434
10435    /** @hide */
10436    @Override
10437    public void sendAccessibilityEventInternal(int eventType) {
10438        if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) {
10439            mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions();
10440        }
10441
10442        // Do not send scroll events since first they are not interesting for
10443        // accessibility and second such events a generated too frequently.
10444        // For details see the implementation of bringTextIntoView().
10445        if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
10446            return;
10447        }
10448        super.sendAccessibilityEventInternal(eventType);
10449    }
10450
10451    /**
10452     * Returns the text that should be exposed to accessibility services.
10453     * <p>
10454     * This approximates what is displayed visually. If the user has specified
10455     * that accessibility services should speak passwords, this method will
10456     * bypass any password transformation method and return unobscured text.
10457     *
10458     * @return the text that should be exposed to accessibility services, may
10459     *         be {@code null} if no text is set
10460     */
10461    @Nullable
10462    private CharSequence getTextForAccessibility() {
10463        // If the text is empty, we must be showing the hint text.
10464        if (TextUtils.isEmpty(mText)) {
10465            return mHint;
10466        }
10467
10468        // Otherwise, return whatever text is being displayed.
10469        return mTransformed;
10470    }
10471
10472    void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
10473            int fromIndex, int removedCount, int addedCount) {
10474        AccessibilityEvent event =
10475                AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
10476        event.setFromIndex(fromIndex);
10477        event.setRemovedCount(removedCount);
10478        event.setAddedCount(addedCount);
10479        event.setBeforeText(beforeText);
10480        sendAccessibilityEventUnchecked(event);
10481    }
10482
10483    /**
10484     * Returns whether this text view is a current input method target.  The
10485     * default implementation just checks with {@link InputMethodManager}.
10486     */
10487    public boolean isInputMethodTarget() {
10488        InputMethodManager imm = InputMethodManager.peekInstance();
10489        return imm != null && imm.isActive(this);
10490    }
10491
10492    static final int ID_SELECT_ALL = android.R.id.selectAll;
10493    static final int ID_UNDO = android.R.id.undo;
10494    static final int ID_REDO = android.R.id.redo;
10495    static final int ID_CUT = android.R.id.cut;
10496    static final int ID_COPY = android.R.id.copy;
10497    static final int ID_PASTE = android.R.id.paste;
10498    static final int ID_SHARE = android.R.id.shareText;
10499    static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText;
10500    static final int ID_REPLACE = android.R.id.replaceText;
10501    static final int ID_ASSIST = android.R.id.textAssist;
10502    static final int ID_AUTOFILL = android.R.id.autofill;
10503
10504    /**
10505     * Called when a context menu option for the text view is selected.  Currently
10506     * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
10507     * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}.
10508     *
10509     * @return true if the context menu item action was performed.
10510     */
10511    public boolean onTextContextMenuItem(int id) {
10512        int min = 0;
10513        int max = mText.length();
10514
10515        if (isFocused()) {
10516            final int selStart = getSelectionStart();
10517            final int selEnd = getSelectionEnd();
10518
10519            min = Math.max(0, Math.min(selStart, selEnd));
10520            max = Math.max(0, Math.max(selStart, selEnd));
10521        }
10522
10523        switch (id) {
10524            case ID_SELECT_ALL:
10525                selectAllText();
10526                return true;
10527
10528            case ID_UNDO:
10529                if (mEditor != null) {
10530                    mEditor.undo();
10531                }
10532                return true;  // Returns true even if nothing was undone.
10533
10534            case ID_REDO:
10535                if (mEditor != null) {
10536                    mEditor.redo();
10537                }
10538                return true;  // Returns true even if nothing was undone.
10539
10540            case ID_PASTE:
10541                paste(min, max, true /* withFormatting */);
10542                return true;
10543
10544            case ID_PASTE_AS_PLAIN_TEXT:
10545                paste(min, max, false /* withFormatting */);
10546                return true;
10547
10548            case ID_CUT:
10549                setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
10550                deleteText_internal(min, max);
10551                return true;
10552
10553            case ID_COPY:
10554                setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
10555                stopTextActionMode();
10556                return true;
10557
10558            case ID_REPLACE:
10559                if (mEditor != null) {
10560                    mEditor.replace();
10561                }
10562                return true;
10563
10564            case ID_SHARE:
10565                shareSelectedText();
10566                return true;
10567
10568            case ID_AUTOFILL:
10569                requestAutofill();
10570                stopTextActionMode();
10571                return true;
10572        }
10573        return false;
10574    }
10575
10576    CharSequence getTransformedText(int start, int end) {
10577        return removeSuggestionSpans(mTransformed.subSequence(start, end));
10578    }
10579
10580    @Override
10581    public boolean performLongClick() {
10582        boolean handled = false;
10583
10584        if (mEditor != null) {
10585            mEditor.mIsBeingLongClicked = true;
10586        }
10587
10588        if (super.performLongClick()) {
10589            handled = true;
10590        }
10591
10592        if (mEditor != null) {
10593            handled |= mEditor.performLongClick(handled);
10594            mEditor.mIsBeingLongClicked = false;
10595        }
10596
10597        if (handled) {
10598            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
10599            if (mEditor != null) mEditor.mDiscardNextActionUp = true;
10600        } else {
10601            MetricsLogger.action(
10602                    mContext,
10603                    MetricsEvent.TEXT_LONGPRESS,
10604                    TextViewMetrics.SUBTYPE_LONG_PRESS_OTHER);
10605        }
10606
10607        return handled;
10608    }
10609
10610    @Override
10611    protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
10612        super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
10613        if (mEditor != null) {
10614            mEditor.onScrollChanged();
10615        }
10616    }
10617
10618    /**
10619     * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
10620     * by the IME or by the spell checker as the user types. This is done by adding
10621     * {@link SuggestionSpan}s to the text.
10622     *
10623     * When suggestions are enabled (default), this list of suggestions will be displayed when the
10624     * user asks for them on these parts of the text. This value depends on the inputType of this
10625     * TextView.
10626     *
10627     * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
10628     *
10629     * In addition, the type variation must be one of
10630     * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
10631     * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
10632     * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
10633     * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
10634     * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
10635     *
10636     * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
10637     *
10638     * @return true if the suggestions popup window is enabled, based on the inputType.
10639     */
10640    public boolean isSuggestionsEnabled() {
10641        if (mEditor == null) return false;
10642        if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
10643            return false;
10644        }
10645        if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
10646
10647        final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
10648        return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL
10649                || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT
10650                || variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE
10651                || variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE
10652                || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
10653    }
10654
10655    /**
10656     * If provided, this ActionMode.Callback will be used to create the ActionMode when text
10657     * selection is initiated in this View.
10658     *
10659     * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy,
10660     * Paste, Replace and Share actions, depending on what this View supports.
10661     *
10662     * <p>A custom implementation can add new entries in the default menu in its
10663     * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)}
10664     * method. The default actions can also be removed from the menu using
10665     * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
10666     * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste},
10667     * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters.
10668     *
10669     * <p>Returning false from
10670     * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)}
10671     * will prevent the action mode from being started.
10672     *
10673     * <p>Action click events should be handled by the custom implementation of
10674     * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode,
10675     * android.view.MenuItem)}.
10676     *
10677     * <p>Note that text selection mode is not started when a TextView receives focus and the
10678     * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
10679     * that case, to allow for quick replacement.
10680     */
10681    public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
10682        createEditorIfNeeded();
10683        mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
10684    }
10685
10686    /**
10687     * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
10688     *
10689     * @return The current custom selection callback.
10690     */
10691    public ActionMode.Callback getCustomSelectionActionModeCallback() {
10692        return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
10693    }
10694
10695    /**
10696     * If provided, this ActionMode.Callback will be used to create the ActionMode when text
10697     * insertion is initiated in this View.
10698     * The standard implementation populates the menu with a subset of Select All,
10699     * Paste and Replace actions, depending on what this View supports.
10700     *
10701     * <p>A custom implementation can add new entries in the default menu in its
10702     * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode,
10703     * android.view.Menu)} method. The default actions can also be removed from the menu using
10704     * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
10705     * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.</p>
10706     *
10707     * <p>Returning false from
10708     * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode,
10709     * android.view.Menu)} will prevent the action mode from being started.</p>
10710     *
10711     * <p>Action click events should be handled by the custom implementation of
10712     * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode,
10713     * android.view.MenuItem)}.</p>
10714     *
10715     * <p>Note that text insertion mode is not started when a TextView receives focus and the
10716     * {@link android.R.attr#selectAllOnFocus} flag has been set.</p>
10717     */
10718    public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) {
10719        createEditorIfNeeded();
10720        mEditor.mCustomInsertionActionModeCallback = actionModeCallback;
10721    }
10722
10723    /**
10724     * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null.
10725     *
10726     * @return The current custom insertion callback.
10727     */
10728    public ActionMode.Callback getCustomInsertionActionModeCallback() {
10729        return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback;
10730    }
10731
10732    /**
10733     * Sets the {@link TextClassifier} for this TextView.
10734     */
10735    public void setTextClassifier(@Nullable TextClassifier textClassifier) {
10736        mTextClassifier = textClassifier;
10737    }
10738
10739    /**
10740     * Returns the {@link TextClassifier} used by this TextView.
10741     * If no TextClassifier has been set, this TextView uses the default set by the
10742     * {@link TextClassificationManager}.
10743     */
10744    @NonNull
10745    public TextClassifier getTextClassifier() {
10746        if (mTextClassifier == null) {
10747            TextClassificationManager tcm =
10748                    mContext.getSystemService(TextClassificationManager.class);
10749            if (tcm != null) {
10750                mTextClassifier = tcm.getDefaultTextClassifier();
10751            } else {
10752                mTextClassifier = TextClassifier.NO_OP;
10753            }
10754        }
10755        return mTextClassifier;
10756    }
10757
10758    /**
10759     * @hide
10760     */
10761    protected void stopTextActionMode() {
10762        if (mEditor != null) {
10763            mEditor.stopTextActionMode();
10764        }
10765    }
10766
10767    boolean canUndo() {
10768        return mEditor != null && mEditor.canUndo();
10769    }
10770
10771    boolean canRedo() {
10772        return mEditor != null && mEditor.canRedo();
10773    }
10774
10775    boolean canCut() {
10776        if (hasPasswordTransformationMethod()) {
10777            return false;
10778        }
10779
10780        if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null
10781                && mEditor.mKeyListener != null) {
10782            return true;
10783        }
10784
10785        return false;
10786    }
10787
10788    boolean canCopy() {
10789        if (hasPasswordTransformationMethod()) {
10790            return false;
10791        }
10792
10793        if (mText.length() > 0 && hasSelection() && mEditor != null) {
10794            return true;
10795        }
10796
10797        return false;
10798    }
10799
10800    boolean canShare() {
10801        if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()) {
10802            return false;
10803        }
10804        return canCopy();
10805    }
10806
10807    boolean isDeviceProvisioned() {
10808        if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) {
10809            mDeviceProvisionedState = Settings.Global.getInt(
10810                    mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0
10811                    ? DEVICE_PROVISIONED_YES
10812                    : DEVICE_PROVISIONED_NO;
10813        }
10814        return mDeviceProvisionedState == DEVICE_PROVISIONED_YES;
10815    }
10816
10817    boolean canPaste() {
10818        return (mText instanceof Editable
10819                && mEditor != null && mEditor.mKeyListener != null
10820                && getSelectionStart() >= 0
10821                && getSelectionEnd() >= 0
10822                && ((ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE))
10823                        .hasPrimaryClip());
10824    }
10825
10826    boolean canProcessText() {
10827        if (getId() == View.NO_ID) {
10828            return false;
10829        }
10830        return canShare();
10831    }
10832
10833    boolean canSelectAllText() {
10834        return canSelectText() && !hasPasswordTransformationMethod()
10835                && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length());
10836    }
10837
10838    boolean selectAllText() {
10839        final int length = mText.length();
10840        Selection.setSelection((Spannable) mText, 0, length);
10841        return length > 0;
10842    }
10843
10844    void replaceSelectionWithText(CharSequence text) {
10845        ((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text);
10846    }
10847
10848    /**
10849     * Paste clipboard content between min and max positions.
10850     */
10851    private void paste(int min, int max, boolean withFormatting) {
10852        ClipboardManager clipboard =
10853                (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
10854        ClipData clip = clipboard.getPrimaryClip();
10855        if (clip != null) {
10856            boolean didFirst = false;
10857            for (int i = 0; i < clip.getItemCount(); i++) {
10858                final CharSequence paste;
10859                if (withFormatting) {
10860                    paste = clip.getItemAt(i).coerceToStyledText(getContext());
10861                } else {
10862                    // Get an item as text and remove all spans by toString().
10863                    final CharSequence text = clip.getItemAt(i).coerceToText(getContext());
10864                    paste = (text instanceof Spanned) ? text.toString() : text;
10865                }
10866                if (paste != null) {
10867                    if (!didFirst) {
10868                        Selection.setSelection((Spannable) mText, max);
10869                        ((Editable) mText).replace(min, max, paste);
10870                        didFirst = true;
10871                    } else {
10872                        ((Editable) mText).insert(getSelectionEnd(), "\n");
10873                        ((Editable) mText).insert(getSelectionEnd(), paste);
10874                    }
10875                }
10876            }
10877            sLastCutCopyOrTextChangedTime = 0;
10878        }
10879    }
10880
10881    private void shareSelectedText() {
10882        String selectedText = getSelectedText();
10883        if (selectedText != null && !selectedText.isEmpty()) {
10884            Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
10885            sharingIntent.setType("text/plain");
10886            sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT);
10887            sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText);
10888            getContext().startActivity(Intent.createChooser(sharingIntent, null));
10889            Selection.setSelection((Spannable) mText, getSelectionEnd());
10890        }
10891    }
10892
10893    private void setPrimaryClip(ClipData clip) {
10894        ClipboardManager clipboard =
10895                (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
10896        clipboard.setPrimaryClip(clip);
10897        sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis();
10898    }
10899
10900    /**
10901     * Get the character offset closest to the specified absolute position. A typical use case is to
10902     * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
10903     *
10904     * @param x The horizontal absolute position of a point on screen
10905     * @param y The vertical absolute position of a point on screen
10906     * @return the character offset for the character whose position is closest to the specified
10907     *  position. Returns -1 if there is no layout.
10908     */
10909    public int getOffsetForPosition(float x, float y) {
10910        if (getLayout() == null) return -1;
10911        final int line = getLineAtCoordinate(y);
10912        final int offset = getOffsetAtCoordinate(line, x);
10913        return offset;
10914    }
10915
10916    float convertToLocalHorizontalCoordinate(float x) {
10917        x -= getTotalPaddingLeft();
10918        // Clamp the position to inside of the view.
10919        x = Math.max(0.0f, x);
10920        x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
10921        x += getScrollX();
10922        return x;
10923    }
10924
10925    int getLineAtCoordinate(float y) {
10926        y -= getTotalPaddingTop();
10927        // Clamp the position to inside of the view.
10928        y = Math.max(0.0f, y);
10929        y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
10930        y += getScrollY();
10931        return getLayout().getLineForVertical((int) y);
10932    }
10933
10934    int getLineAtCoordinateUnclamped(float y) {
10935        y -= getTotalPaddingTop();
10936        y += getScrollY();
10937        return getLayout().getLineForVertical((int) y);
10938    }
10939
10940    int getOffsetAtCoordinate(int line, float x) {
10941        x = convertToLocalHorizontalCoordinate(x);
10942        return getLayout().getOffsetForHorizontal(line, x);
10943    }
10944
10945    @Override
10946    public boolean onDragEvent(DragEvent event) {
10947        switch (event.getAction()) {
10948            case DragEvent.ACTION_DRAG_STARTED:
10949                return mEditor != null && mEditor.hasInsertionController();
10950
10951            case DragEvent.ACTION_DRAG_ENTERED:
10952                TextView.this.requestFocus();
10953                return true;
10954
10955            case DragEvent.ACTION_DRAG_LOCATION:
10956                final int offset = getOffsetForPosition(event.getX(), event.getY());
10957                Selection.setSelection((Spannable) mText, offset);
10958                return true;
10959
10960            case DragEvent.ACTION_DROP:
10961                if (mEditor != null) mEditor.onDrop(event);
10962                return true;
10963
10964            case DragEvent.ACTION_DRAG_ENDED:
10965            case DragEvent.ACTION_DRAG_EXITED:
10966            default:
10967                return true;
10968        }
10969    }
10970
10971    boolean isInBatchEditMode() {
10972        if (mEditor == null) return false;
10973        final Editor.InputMethodState ims = mEditor.mInputMethodState;
10974        if (ims != null) {
10975            return ims.mBatchEditNesting > 0;
10976        }
10977        return mEditor.mInBatchEditControllers;
10978    }
10979
10980    @Override
10981    public void onRtlPropertiesChanged(int layoutDirection) {
10982        super.onRtlPropertiesChanged(layoutDirection);
10983
10984        final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic();
10985        if (mTextDir != newTextDir) {
10986            mTextDir = newTextDir;
10987            if (mLayout != null) {
10988                checkForRelayout();
10989            }
10990        }
10991    }
10992
10993    /**
10994     * @hide
10995     */
10996    protected TextDirectionHeuristic getTextDirectionHeuristic() {
10997        if (hasPasswordTransformationMethod()) {
10998            // passwords fields should be LTR
10999            return TextDirectionHeuristics.LTR;
11000        }
11001
11002        // Always need to resolve layout direction first
11003        final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
11004
11005        // Now, we can select the heuristic
11006        switch (getTextDirection()) {
11007            default:
11008            case TEXT_DIRECTION_FIRST_STRONG:
11009                return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
11010                        TextDirectionHeuristics.FIRSTSTRONG_LTR);
11011            case TEXT_DIRECTION_ANY_RTL:
11012                return TextDirectionHeuristics.ANYRTL_LTR;
11013            case TEXT_DIRECTION_LTR:
11014                return TextDirectionHeuristics.LTR;
11015            case TEXT_DIRECTION_RTL:
11016                return TextDirectionHeuristics.RTL;
11017            case TEXT_DIRECTION_LOCALE:
11018                return TextDirectionHeuristics.LOCALE;
11019            case TEXT_DIRECTION_FIRST_STRONG_LTR:
11020                return TextDirectionHeuristics.FIRSTSTRONG_LTR;
11021            case TEXT_DIRECTION_FIRST_STRONG_RTL:
11022                return TextDirectionHeuristics.FIRSTSTRONG_RTL;
11023        }
11024    }
11025
11026    /**
11027     * @hide
11028     */
11029    @Override
11030    public void onResolveDrawables(int layoutDirection) {
11031        // No need to resolve twice
11032        if (mLastLayoutDirection == layoutDirection) {
11033            return;
11034        }
11035        mLastLayoutDirection = layoutDirection;
11036
11037        // Resolve drawables
11038        if (mDrawables != null) {
11039            if (mDrawables.resolveWithLayoutDirection(layoutDirection)) {
11040                prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]);
11041                prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]);
11042                applyCompoundDrawableTint();
11043            }
11044        }
11045    }
11046
11047    /**
11048     * Prepares a drawable for display by propagating layout direction and
11049     * drawable state.
11050     *
11051     * @param dr the drawable to prepare
11052     */
11053    private void prepareDrawableForDisplay(@Nullable Drawable dr) {
11054        if (dr == null) {
11055            return;
11056        }
11057
11058        dr.setLayoutDirection(getLayoutDirection());
11059
11060        if (dr.isStateful()) {
11061            dr.setState(getDrawableState());
11062            dr.jumpToCurrentState();
11063        }
11064    }
11065
11066    /**
11067     * @hide
11068     */
11069    protected void resetResolvedDrawables() {
11070        super.resetResolvedDrawables();
11071        mLastLayoutDirection = -1;
11072    }
11073
11074    /**
11075     * @hide
11076     */
11077    protected void viewClicked(InputMethodManager imm) {
11078        if (imm != null) {
11079            imm.viewClicked(this);
11080        }
11081    }
11082
11083    /**
11084     * Deletes the range of text [start, end[.
11085     * @hide
11086     */
11087    protected void deleteText_internal(int start, int end) {
11088        ((Editable) mText).delete(start, end);
11089    }
11090
11091    /**
11092     * Replaces the range of text [start, end[ by replacement text
11093     * @hide
11094     */
11095    protected void replaceText_internal(int start, int end, CharSequence text) {
11096        ((Editable) mText).replace(start, end, text);
11097    }
11098
11099    /**
11100     * Sets a span on the specified range of text
11101     * @hide
11102     */
11103    protected void setSpan_internal(Object span, int start, int end, int flags) {
11104        ((Editable) mText).setSpan(span, start, end, flags);
11105    }
11106
11107    /**
11108     * Moves the cursor to the specified offset position in text
11109     * @hide
11110     */
11111    protected void setCursorPosition_internal(int start, int end) {
11112        Selection.setSelection(((Editable) mText), start, end);
11113    }
11114
11115    /**
11116     * An Editor should be created as soon as any of the editable-specific fields (grouped
11117     * inside the Editor object) is assigned to a non-default value.
11118     * This method will create the Editor if needed.
11119     *
11120     * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
11121     * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
11122     * Editor for backward compatibility, as soon as one of these fields is assigned.
11123     *
11124     * Also note that for performance reasons, the mEditor is created when needed, but not
11125     * reset when no more edit-specific fields are needed.
11126     */
11127    private void createEditorIfNeeded() {
11128        if (mEditor == null) {
11129            mEditor = new Editor(this);
11130        }
11131    }
11132
11133    /**
11134     * @hide
11135     */
11136    @Override
11137    public CharSequence getIterableTextForAccessibility() {
11138        return mText;
11139    }
11140
11141    private void ensureIterableTextForAccessibilitySelectable() {
11142        if (!(mText instanceof Spannable)) {
11143            setText(mText, BufferType.SPANNABLE);
11144        }
11145    }
11146
11147    /**
11148     * @hide
11149     */
11150    @Override
11151    public TextSegmentIterator getIteratorForGranularity(int granularity) {
11152        switch (granularity) {
11153            case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
11154                Spannable text = (Spannable) getIterableTextForAccessibility();
11155                if (!TextUtils.isEmpty(text) && getLayout() != null) {
11156                    AccessibilityIterators.LineTextSegmentIterator iterator =
11157                            AccessibilityIterators.LineTextSegmentIterator.getInstance();
11158                    iterator.initialize(text, getLayout());
11159                    return iterator;
11160                }
11161            } break;
11162            case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
11163                Spannable text = (Spannable) getIterableTextForAccessibility();
11164                if (!TextUtils.isEmpty(text) && getLayout() != null) {
11165                    AccessibilityIterators.PageTextSegmentIterator iterator =
11166                            AccessibilityIterators.PageTextSegmentIterator.getInstance();
11167                    iterator.initialize(this);
11168                    return iterator;
11169                }
11170            } break;
11171        }
11172        return super.getIteratorForGranularity(granularity);
11173    }
11174
11175    /**
11176     * @hide
11177     */
11178    @Override
11179    public int getAccessibilitySelectionStart() {
11180        return getSelectionStart();
11181    }
11182
11183    /**
11184     * @hide
11185     */
11186    public boolean isAccessibilitySelectionExtendable() {
11187        return true;
11188    }
11189
11190    /**
11191     * @hide
11192     */
11193    @Override
11194    public int getAccessibilitySelectionEnd() {
11195        return getSelectionEnd();
11196    }
11197
11198    /**
11199     * @hide
11200     */
11201    @Override
11202    public void setAccessibilitySelection(int start, int end) {
11203        if (getAccessibilitySelectionStart() == start
11204                && getAccessibilitySelectionEnd() == end) {
11205            return;
11206        }
11207        CharSequence text = getIterableTextForAccessibility();
11208        if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
11209            Selection.setSelection((Spannable) text, start, end);
11210        } else {
11211            Selection.removeSelection((Spannable) text);
11212        }
11213        // Hide all selection controllers used for adjusting selection
11214        // since we are doing so explicitlty by other means and these
11215        // controllers interact with how selection behaves.
11216        if (mEditor != null) {
11217            mEditor.hideCursorAndSpanControllers();
11218            mEditor.stopTextActionMode();
11219        }
11220    }
11221
11222    /** @hide */
11223    @Override
11224    protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
11225        super.encodeProperties(stream);
11226
11227        TruncateAt ellipsize = getEllipsize();
11228        stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name());
11229        stream.addProperty("text:textSize", getTextSize());
11230        stream.addProperty("text:scaledTextSize", getScaledTextSize());
11231        stream.addProperty("text:typefaceStyle", getTypefaceStyle());
11232        stream.addProperty("text:selectionStart", getSelectionStart());
11233        stream.addProperty("text:selectionEnd", getSelectionEnd());
11234        stream.addProperty("text:curTextColor", mCurTextColor);
11235        stream.addProperty("text:text", mText == null ? null : mText.toString());
11236        stream.addProperty("text:gravity", mGravity);
11237    }
11238
11239    /**
11240     * User interface state that is stored by TextView for implementing
11241     * {@link View#onSaveInstanceState}.
11242     */
11243    public static class SavedState extends BaseSavedState {
11244        int selStart = -1;
11245        int selEnd = -1;
11246        CharSequence text;
11247        boolean frozenWithFocus;
11248        CharSequence error;
11249        ParcelableParcel editorState;  // Optional state from Editor.
11250
11251        SavedState(Parcelable superState) {
11252            super(superState);
11253        }
11254
11255        @Override
11256        public void writeToParcel(Parcel out, int flags) {
11257            super.writeToParcel(out, flags);
11258            out.writeInt(selStart);
11259            out.writeInt(selEnd);
11260            out.writeInt(frozenWithFocus ? 1 : 0);
11261            TextUtils.writeToParcel(text, out, flags);
11262
11263            if (error == null) {
11264                out.writeInt(0);
11265            } else {
11266                out.writeInt(1);
11267                TextUtils.writeToParcel(error, out, flags);
11268            }
11269
11270            if (editorState == null) {
11271                out.writeInt(0);
11272            } else {
11273                out.writeInt(1);
11274                editorState.writeToParcel(out, flags);
11275            }
11276        }
11277
11278        @Override
11279        public String toString() {
11280            String str = "TextView.SavedState{"
11281                    + Integer.toHexString(System.identityHashCode(this))
11282                    + " start=" + selStart + " end=" + selEnd;
11283            if (text != null) {
11284                str += " text=" + text;
11285            }
11286            return str + "}";
11287        }
11288
11289        @SuppressWarnings("hiding")
11290        public static final Parcelable.Creator<SavedState> CREATOR =
11291                new Parcelable.Creator<SavedState>() {
11292                    public SavedState createFromParcel(Parcel in) {
11293                        return new SavedState(in);
11294                    }
11295
11296                    public SavedState[] newArray(int size) {
11297                        return new SavedState[size];
11298                    }
11299                };
11300
11301        private SavedState(Parcel in) {
11302            super(in);
11303            selStart = in.readInt();
11304            selEnd = in.readInt();
11305            frozenWithFocus = (in.readInt() != 0);
11306            text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
11307
11308            if (in.readInt() != 0) {
11309                error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
11310            }
11311
11312            if (in.readInt() != 0) {
11313                editorState = ParcelableParcel.CREATOR.createFromParcel(in);
11314            }
11315        }
11316    }
11317
11318    private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
11319        private char[] mChars;
11320        private int mStart, mLength;
11321
11322        public CharWrapper(char[] chars, int start, int len) {
11323            mChars = chars;
11324            mStart = start;
11325            mLength = len;
11326        }
11327
11328        /* package */ void set(char[] chars, int start, int len) {
11329            mChars = chars;
11330            mStart = start;
11331            mLength = len;
11332        }
11333
11334        public int length() {
11335            return mLength;
11336        }
11337
11338        public char charAt(int off) {
11339            return mChars[off + mStart];
11340        }
11341
11342        @Override
11343        public String toString() {
11344            return new String(mChars, mStart, mLength);
11345        }
11346
11347        public CharSequence subSequence(int start, int end) {
11348            if (start < 0 || end < 0 || start > mLength || end > mLength) {
11349                throw new IndexOutOfBoundsException(start + ", " + end);
11350            }
11351
11352            return new String(mChars, start + mStart, end - start);
11353        }
11354
11355        public void getChars(int start, int end, char[] buf, int off) {
11356            if (start < 0 || end < 0 || start > mLength || end > mLength) {
11357                throw new IndexOutOfBoundsException(start + ", " + end);
11358            }
11359
11360            System.arraycopy(mChars, start + mStart, buf, off, end - start);
11361        }
11362
11363        @Override
11364        public void drawText(BaseCanvas c, int start, int end,
11365                             float x, float y, Paint p) {
11366            c.drawText(mChars, start + mStart, end - start, x, y, p);
11367        }
11368
11369        @Override
11370        public void drawTextRun(BaseCanvas c, int start, int end,
11371                int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) {
11372            int count = end - start;
11373            int contextCount = contextEnd - contextStart;
11374            c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
11375                    contextCount, x, y, isRtl, p);
11376        }
11377
11378        public float measureText(int start, int end, Paint p) {
11379            return p.measureText(mChars, start + mStart, end - start);
11380        }
11381
11382        public int getTextWidths(int start, int end, float[] widths, Paint p) {
11383            return p.getTextWidths(mChars, start + mStart, end - start, widths);
11384        }
11385
11386        public float getTextRunAdvances(int start, int end, int contextStart,
11387                int contextEnd, boolean isRtl, float[] advances, int advancesIndex,
11388                Paint p) {
11389            int count = end - start;
11390            int contextCount = contextEnd - contextStart;
11391            return p.getTextRunAdvances(mChars, start + mStart, count,
11392                    contextStart + mStart, contextCount, isRtl, advances,
11393                    advancesIndex);
11394        }
11395
11396        public int getTextRunCursor(int contextStart, int contextEnd, int dir,
11397                int offset, int cursorOpt, Paint p) {
11398            int contextCount = contextEnd - contextStart;
11399            return p.getTextRunCursor(mChars, contextStart + mStart,
11400                    contextCount, dir, offset + mStart, cursorOpt);
11401        }
11402    }
11403
11404    private static final class Marquee {
11405        // TODO: Add an option to configure this
11406        private static final float MARQUEE_DELTA_MAX = 0.07f;
11407        private static final int MARQUEE_DELAY = 1200;
11408        private static final int MARQUEE_DP_PER_SECOND = 30;
11409
11410        private static final byte MARQUEE_STOPPED = 0x0;
11411        private static final byte MARQUEE_STARTING = 0x1;
11412        private static final byte MARQUEE_RUNNING = 0x2;
11413
11414        private final WeakReference<TextView> mView;
11415        private final Choreographer mChoreographer;
11416
11417        private byte mStatus = MARQUEE_STOPPED;
11418        private final float mPixelsPerSecond;
11419        private float mMaxScroll;
11420        private float mMaxFadeScroll;
11421        private float mGhostStart;
11422        private float mGhostOffset;
11423        private float mFadeStop;
11424        private int mRepeatLimit;
11425
11426        private float mScroll;
11427        private long mLastAnimationMs;
11428
11429        Marquee(TextView v) {
11430            final float density = v.getContext().getResources().getDisplayMetrics().density;
11431            mPixelsPerSecond = MARQUEE_DP_PER_SECOND * density;
11432            mView = new WeakReference<TextView>(v);
11433            mChoreographer = Choreographer.getInstance();
11434        }
11435
11436        private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() {
11437            @Override
11438            public void doFrame(long frameTimeNanos) {
11439                tick();
11440            }
11441        };
11442
11443        private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
11444            @Override
11445            public void doFrame(long frameTimeNanos) {
11446                mStatus = MARQUEE_RUNNING;
11447                mLastAnimationMs = mChoreographer.getFrameTime();
11448                tick();
11449            }
11450        };
11451
11452        private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
11453            @Override
11454            public void doFrame(long frameTimeNanos) {
11455                if (mStatus == MARQUEE_RUNNING) {
11456                    if (mRepeatLimit >= 0) {
11457                        mRepeatLimit--;
11458                    }
11459                    start(mRepeatLimit);
11460                }
11461            }
11462        };
11463
11464        void tick() {
11465            if (mStatus != MARQUEE_RUNNING) {
11466                return;
11467            }
11468
11469            mChoreographer.removeFrameCallback(mTickCallback);
11470
11471            final TextView textView = mView.get();
11472            if (textView != null && (textView.isFocused() || textView.isSelected())) {
11473                long currentMs = mChoreographer.getFrameTime();
11474                long deltaMs = currentMs - mLastAnimationMs;
11475                mLastAnimationMs = currentMs;
11476                float deltaPx = deltaMs / 1000f * mPixelsPerSecond;
11477                mScroll += deltaPx;
11478                if (mScroll > mMaxScroll) {
11479                    mScroll = mMaxScroll;
11480                    mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
11481                } else {
11482                    mChoreographer.postFrameCallback(mTickCallback);
11483                }
11484                textView.invalidate();
11485            }
11486        }
11487
11488        void stop() {
11489            mStatus = MARQUEE_STOPPED;
11490            mChoreographer.removeFrameCallback(mStartCallback);
11491            mChoreographer.removeFrameCallback(mRestartCallback);
11492            mChoreographer.removeFrameCallback(mTickCallback);
11493            resetScroll();
11494        }
11495
11496        private void resetScroll() {
11497            mScroll = 0.0f;
11498            final TextView textView = mView.get();
11499            if (textView != null) textView.invalidate();
11500        }
11501
11502        void start(int repeatLimit) {
11503            if (repeatLimit == 0) {
11504                stop();
11505                return;
11506            }
11507            mRepeatLimit = repeatLimit;
11508            final TextView textView = mView.get();
11509            if (textView != null && textView.mLayout != null) {
11510                mStatus = MARQUEE_STARTING;
11511                mScroll = 0.0f;
11512                final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft()
11513                        - textView.getCompoundPaddingRight();
11514                final float lineWidth = textView.mLayout.getLineWidth(0);
11515                final float gap = textWidth / 3.0f;
11516                mGhostStart = lineWidth - textWidth + gap;
11517                mMaxScroll = mGhostStart + textWidth;
11518                mGhostOffset = lineWidth + gap;
11519                mFadeStop = lineWidth + textWidth / 6.0f;
11520                mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
11521
11522                textView.invalidate();
11523                mChoreographer.postFrameCallback(mStartCallback);
11524            }
11525        }
11526
11527        float getGhostOffset() {
11528            return mGhostOffset;
11529        }
11530
11531        float getScroll() {
11532            return mScroll;
11533        }
11534
11535        float getMaxFadeScroll() {
11536            return mMaxFadeScroll;
11537        }
11538
11539        boolean shouldDrawLeftFade() {
11540            return mScroll <= mFadeStop;
11541        }
11542
11543        boolean shouldDrawGhost() {
11544            return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
11545        }
11546
11547        boolean isRunning() {
11548            return mStatus == MARQUEE_RUNNING;
11549        }
11550
11551        boolean isStopped() {
11552            return mStatus == MARQUEE_STOPPED;
11553        }
11554    }
11555
11556    private class ChangeWatcher implements TextWatcher, SpanWatcher {
11557
11558        private CharSequence mBeforeText;
11559
11560        public void beforeTextChanged(CharSequence buffer, int start,
11561                                      int before, int after) {
11562            if (DEBUG_EXTRACT) {
11563                Log.v(LOG_TAG, "beforeTextChanged start=" + start
11564                        + " before=" + before + " after=" + after + ": " + buffer);
11565            }
11566
11567            if (AccessibilityManager.getInstance(mContext).isEnabled()) {
11568                mBeforeText = buffer.toString();
11569            }
11570
11571            TextView.this.sendBeforeTextChanged(buffer, start, before, after);
11572        }
11573
11574        public void onTextChanged(CharSequence buffer, int start, int before, int after) {
11575            if (DEBUG_EXTRACT) {
11576                Log.v(LOG_TAG, "onTextChanged start=" + start
11577                        + " before=" + before + " after=" + after + ": " + buffer);
11578            }
11579            TextView.this.handleTextChanged(buffer, start, before, after);
11580
11581            if (AccessibilityManager.getInstance(mContext).isEnabled()
11582                    && (isFocused() || isSelected() && isShown())) {
11583                sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
11584                mBeforeText = null;
11585            }
11586        }
11587
11588        public void afterTextChanged(Editable buffer) {
11589            if (DEBUG_EXTRACT) {
11590                Log.v(LOG_TAG, "afterTextChanged: " + buffer);
11591            }
11592            TextView.this.sendAfterTextChanged(buffer);
11593
11594            if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
11595                MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
11596            }
11597        }
11598
11599        public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
11600            if (DEBUG_EXTRACT) {
11601                Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
11602                        + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
11603            }
11604            TextView.this.spanChange(buf, what, s, st, e, en);
11605        }
11606
11607        public void onSpanAdded(Spannable buf, Object what, int s, int e) {
11608            if (DEBUG_EXTRACT) {
11609                Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e + " what=" + what + ": " + buf);
11610            }
11611            TextView.this.spanChange(buf, what, -1, s, -1, e);
11612        }
11613
11614        public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
11615            if (DEBUG_EXTRACT) {
11616                Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e + " what=" + what + ": " + buf);
11617            }
11618            TextView.this.spanChange(buf, what, s, -1, e, -1);
11619        }
11620    }
11621}
11622