1 /*
2  * Copyright (C) 2006 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.widget;
18 
19 import android.app.compat.CompatChanges;
20 import android.compat.annotation.ChangeId;
21 import android.compat.annotation.EnabledSince;
22 import android.content.Context;
23 import android.content.res.Resources;
24 import android.content.res.TypedArray;
25 import android.os.Build;
26 import android.text.Editable;
27 import android.text.Selection;
28 import android.text.Spannable;
29 import android.text.TextUtils;
30 import android.text.method.ArrowKeyMovementMethod;
31 import android.text.method.MovementMethod;
32 import android.text.style.SpanUtils;
33 import android.util.AttributeSet;
34 import android.view.KeyEvent;
35 
36 import com.android.internal.R;
37 
38 /*
39  * This is supposed to be a *very* thin veneer over TextView.
40  * Do not make any changes here that do anything that a TextView
41  * with a key listener and a movement method wouldn't do!
42  */
43 
44 /**
45  * A user interface element for entering and modifying text.
46  * When you define an edit text widget, you must specify the
47  * {@link android.R.styleable#TextView_inputType}
48  * attribute. For example, for plain text input set inputType to "text":
49  * <p>
50  * <pre>
51  * &lt;EditText
52  *     android:id="@+id/plain_text_input"
53  *     android:layout_height="wrap_content"
54  *     android:layout_width="match_parent"
55  *     android:inputType="text"/&gt;</pre>
56  *
57  * Choosing the input type configures the keyboard type that is shown, acceptable characters,
58  * and appearance of the edit text.
59  * For example, if you want to accept a secret number, like a unique pin or serial number,
60  * you can set inputType to "numericPassword".
61  * An inputType of "numericPassword" results in an edit text that accepts numbers only,
62  * shows a numeric keyboard when focused, and masks the text that is entered for privacy.
63  * <p>
64  * See the <a href="{@docRoot}guide/topics/ui/controls/text.html">Text Fields</a>
65  * guide for examples of other
66  * {@link android.R.styleable#TextView_inputType} settings.
67  * </p>
68  * <p>You also can receive callbacks as a user changes text by
69  * adding a {@link android.text.TextWatcher} to the edit text.
70  * This is useful when you want to add auto-save functionality as changes are made,
71  * or validate the format of user input, for example.
72  * You add a text watcher using the {@link TextView#addTextChangedListener} method.
73  * </p>
74  * <p>
75  * This widget does not support auto-sizing text.
76  * <p>
77  * <b>XML attributes</b>
78  * <p>
79  * See {@link android.R.styleable#EditText EditText Attributes},
80  * {@link android.R.styleable#TextView TextView Attributes},
81  * {@link android.R.styleable#View View Attributes}
82  *
83  * @attr ref android.R.styleable#EditText_enableTextStylingShortcuts
84  */
85 public class EditText extends TextView {
86 
87     // True if the style shortcut is enabled.
88     private boolean mStyleShortcutsEnabled = false;
89 
90     private static final int ID_BOLD = android.R.id.bold;
91     private static final int ID_ITALIC = android.R.id.italic;
92     private static final int ID_UNDERLINE = android.R.id.underline;
93 
94     /** @hide */
95     @ChangeId
96     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
97     public static final long LINE_HEIGHT_FOR_LOCALE = 303326708L;
98 
EditText(Context context)99     public EditText(Context context) {
100         this(context, null);
101     }
102 
EditText(Context context, AttributeSet attrs)103     public EditText(Context context, AttributeSet attrs) {
104         this(context, attrs, com.android.internal.R.attr.editTextStyle);
105     }
106 
EditText(Context context, AttributeSet attrs, int defStyleAttr)107     public EditText(Context context, AttributeSet attrs, int defStyleAttr) {
108         this(context, attrs, defStyleAttr, 0);
109     }
110 
EditText(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)111     public EditText(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
112         super(context, attrs, defStyleAttr, defStyleRes);
113 
114         final Resources.Theme theme = context.getTheme();
115         final TypedArray a = theme.obtainStyledAttributes(attrs,
116                 com.android.internal.R.styleable.EditText, defStyleAttr, defStyleRes);
117 
118         try {
119             final int n = a.getIndexCount();
120             for (int i = 0; i < n; ++i) {
121                 int attr = a.getIndex(i);
122                 switch (attr) {
123                     case com.android.internal.R.styleable.EditText_enableTextStylingShortcuts:
124                         mStyleShortcutsEnabled = a.getBoolean(attr, false);
125                         break;
126                 }
127             }
128         } finally {
129             a.recycle();
130         }
131 
132         boolean hasUseLocalePreferredLineHeightForMinimumInt = false;
133         boolean useLocalePreferredLineHeightForMinimumInt = false;
134         TypedArray tvArray = theme.obtainStyledAttributes(attrs,
135                 com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
136         try {
137             hasUseLocalePreferredLineHeightForMinimumInt =
138                     tvArray.hasValue(R.styleable.TextView_useLocalePreferredLineHeightForMinimum);
139             if (hasUseLocalePreferredLineHeightForMinimumInt) {
140                 useLocalePreferredLineHeightForMinimumInt = tvArray.getBoolean(
141                         R.styleable.TextView_useLocalePreferredLineHeightForMinimum, false);
142             }
143         } finally {
144             tvArray.recycle();
145         }
146         if (!hasUseLocalePreferredLineHeightForMinimumInt) {
147             useLocalePreferredLineHeightForMinimumInt =
148                     CompatChanges.isChangeEnabled(LINE_HEIGHT_FOR_LOCALE);
149         }
150         setLocalePreferredLineHeightForMinimumUsed(useLocalePreferredLineHeightForMinimumInt);
151     }
152 
153     @Override
getFreezesText()154     public boolean getFreezesText() {
155         return true;
156     }
157 
158     @Override
getDefaultEditable()159     protected boolean getDefaultEditable() {
160         return true;
161     }
162 
163     @Override
getDefaultMovementMethod()164     protected MovementMethod getDefaultMovementMethod() {
165         return ArrowKeyMovementMethod.getInstance();
166     }
167 
168     @Override
getText()169     public Editable getText() {
170         CharSequence text = super.getText();
171         // This can only happen during construction.
172         if (text == null) {
173             return null;
174         }
175         if (text instanceof Editable) {
176             return (Editable) text;
177         }
178         super.setText(text, BufferType.EDITABLE);
179         return (Editable) super.getText();
180     }
181 
182     @Override
setText(CharSequence text, BufferType type)183     public void setText(CharSequence text, BufferType type) {
184         super.setText(text, BufferType.EDITABLE);
185     }
186 
187     /**
188      * Convenience for {@link Selection#setSelection(Spannable, int, int)}.
189      */
setSelection(int start, int stop)190     public void setSelection(int start, int stop) {
191         Selection.setSelection(getText(), start, stop);
192     }
193 
194     /**
195      * Convenience for {@link Selection#setSelection(Spannable, int)}.
196      */
setSelection(int index)197     public void setSelection(int index) {
198         Selection.setSelection(getText(), index);
199     }
200 
201     /**
202      * Convenience for {@link Selection#selectAll}.
203      */
selectAll()204     public void selectAll() {
205         Selection.selectAll(getText());
206     }
207 
208     /**
209      * Convenience for {@link Selection#extendSelection}.
210      */
extendSelection(int index)211     public void extendSelection(int index) {
212         Selection.extendSelection(getText(), index);
213     }
214 
215     /**
216      * Causes words in the text that are longer than the view's width to be ellipsized instead of
217      * broken in the middle. {@link TextUtils.TruncateAt#MARQUEE
218      * TextUtils.TruncateAt#MARQUEE} is not supported.
219      *
220      * @param ellipsis Type of ellipsis to be applied.
221      * @throws IllegalArgumentException When the value of <code>ellipsis</code> parameter is
222      *      {@link TextUtils.TruncateAt#MARQUEE}.
223      * @see TextView#setEllipsize(TextUtils.TruncateAt)
224      */
225     @Override
setEllipsize(TextUtils.TruncateAt ellipsis)226     public void setEllipsize(TextUtils.TruncateAt ellipsis) {
227         if (ellipsis == TextUtils.TruncateAt.MARQUEE) {
228             throw new IllegalArgumentException("EditText cannot use the ellipsize mode "
229                     + "TextUtils.TruncateAt.MARQUEE");
230         }
231         super.setEllipsize(ellipsis);
232     }
233 
234     @Override
getAccessibilityClassName()235     public CharSequence getAccessibilityClassName() {
236         return EditText.class.getName();
237     }
238 
239     /** @hide */
240     @Override
supportsAutoSizeText()241     protected boolean supportsAutoSizeText() {
242         return false;
243     }
244 
245     @Override
onKeyShortcut(int keyCode, KeyEvent event)246     public boolean onKeyShortcut(int keyCode, KeyEvent event) {
247         if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
248             // Handle Ctrl-only shortcuts.
249             switch (keyCode) {
250                 case KeyEvent.KEYCODE_B:
251                     if (mStyleShortcutsEnabled && hasSelection()) {
252                         return onTextContextMenuItem(ID_BOLD);
253                     }
254                     break;
255                 case KeyEvent.KEYCODE_I:
256                     if (mStyleShortcutsEnabled && hasSelection()) {
257                         return onTextContextMenuItem(ID_ITALIC);
258                     }
259                     break;
260                 case KeyEvent.KEYCODE_U:
261                     if (mStyleShortcutsEnabled && hasSelection()) {
262                         return onTextContextMenuItem(ID_UNDERLINE);
263                     }
264                     break;
265             }
266         }
267         return super.onKeyShortcut(keyCode, event);
268     }
269 
270     @Override
onTextContextMenuItem(int id)271     public boolean onTextContextMenuItem(int id) {
272         // TODO: Move to switch-case once the resource ID is finalized.
273         if (id == ID_BOLD || id == ID_ITALIC || id == ID_UNDERLINE) {
274             return performStylingAction(id);
275         }
276         return super.onTextContextMenuItem(id);
277     }
278 
performStylingAction(int actionId)279     private boolean performStylingAction(int actionId) {
280         final int selectionStart = getSelectionStart();
281         final int selectionEnd = getSelectionEnd();
282         if (selectionStart < 0 || selectionEnd < 0) {
283             return false;  // There is no selection.
284         }
285         int min = Math.min(selectionStart, selectionEnd);
286         int max = Math.max(selectionStart, selectionEnd);
287 
288 
289         Spannable spannable = getText();
290         if (actionId == ID_BOLD) {
291             return SpanUtils.toggleBold(spannable, min, max);
292         } else if (actionId == ID_ITALIC) {
293             return SpanUtils.toggleItalic(spannable, min, max);
294         } else if (actionId == ID_UNDERLINE) {
295             return SpanUtils.toggleUnderline(spannable, min, max);
296         }
297 
298         return false;
299     }
300 
301     /**
302      * Enables styls shortcuts, e.g. Ctrl+B for making text bold.
303      *
304      * @param enabled true for enabled, false for disabled.
305      */
setStyleShortcutsEnabled(boolean enabled)306     public void setStyleShortcutsEnabled(boolean enabled) {
307         mStyleShortcutsEnabled = enabled;
308     }
309 
310     /**
311      * Return true if style shortcut is enabled, otherwise returns false.
312      * @return true if style shortcut is enabled, otherwise returns false.
313      */
isStyleShortcutEnabled()314     public boolean isStyleShortcutEnabled() {
315         return mStyleShortcutsEnabled;
316     }
317 }
318