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