• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 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.text.method;
18 
19 import android.content.ContentResolver;
20 import android.content.Context;
21 import android.database.ContentObserver;
22 import android.os.Handler;
23 import android.provider.Settings;
24 import android.provider.Settings.System;
25 import android.text.Editable;
26 import android.text.InputType;
27 import android.text.NoCopySpan;
28 import android.text.Selection;
29 import android.text.SpanWatcher;
30 import android.text.Spannable;
31 import android.text.TextUtils;
32 import android.view.KeyCharacterMap;
33 import android.view.KeyEvent;
34 import android.view.View;
35 
36 import java.lang.ref.WeakReference;
37 
38 /**
39  * This is the key listener for typing normal text.  It delegates to
40  * other key listeners appropriate to the current keyboard and language.
41  * <p></p>
42  * As for all implementations of {@link KeyListener}, this class is only concerned
43  * with hardware keyboards.  Software input methods have no obligation to trigger
44  * the methods in this class.
45  */
46 @android.ravenwood.annotation.RavenwoodKeepWholeClass
47 public class TextKeyListener extends BaseKeyListener implements SpanWatcher {
48     private static TextKeyListener[] sInstance =
49         new TextKeyListener[Capitalize.values().length * 2];
50 
51     /* package */ static final Object ACTIVE = new NoCopySpan.Concrete();
52     /* package */ static final Object CAPPED = new NoCopySpan.Concrete();
53     /* package */ static final Object INHIBIT_REPLACEMENT = new NoCopySpan.Concrete();
54     /* package */ static final Object LAST_TYPED = new NoCopySpan.Concrete();
55 
56     private Capitalize mAutoCap;
57     private boolean mAutoText;
58 
59     private int mPrefs;
60     private boolean mPrefsInited;
61 
62     /* package */ static final int AUTO_CAP = 1;
63     /* package */ static final int AUTO_TEXT = 2;
64     /* package */ static final int AUTO_PERIOD = 4;
65     /* package */ static final int SHOW_PASSWORD = 8;
66     private WeakReference<ContentResolver> mResolver;
67     private TextKeyListener.SettingsObserver mObserver;
68 
69     /**
70      * Creates a new TextKeyListener with the specified capitalization
71      * and correction properties.
72      *
73      * @param cap when, if ever, to automatically capitalize.
74      * @param autotext whether to automatically do spelling corrections.
75      */
TextKeyListener(Capitalize cap, boolean autotext)76     public TextKeyListener(Capitalize cap, boolean autotext) {
77         mAutoCap = cap;
78         mAutoText = autotext;
79     }
80 
81     /**
82      * Returns a new or existing instance with the specified capitalization
83      * and correction properties.
84      *
85      * @param cap when, if ever, to automatically capitalize.
86      * @param autotext whether to automatically do spelling corrections.
87      */
getInstance(boolean autotext, Capitalize cap)88     public static TextKeyListener getInstance(boolean autotext,
89                                               Capitalize cap) {
90         int off = cap.ordinal() * 2 + (autotext ? 1 : 0);
91 
92         if (sInstance[off] == null) {
93             sInstance[off] = new TextKeyListener(cap, autotext);
94         }
95 
96         return sInstance[off];
97     }
98 
99     /**
100      * Returns a new or existing instance with no automatic capitalization
101      * or correction.
102      */
getInstance()103     public static TextKeyListener getInstance() {
104         return getInstance(false, Capitalize.NONE);
105     }
106 
107     /**
108      * Returns whether it makes sense to automatically capitalize at the
109      * specified position in the specified text, with the specified rules.
110      *
111      * @param cap the capitalization rules to consider.
112      * @param cs the text in which an insertion is being made.
113      * @param off the offset into that text where the insertion is being made.
114      *
115      * @return whether the character being inserted should be capitalized.
116      */
shouldCap(Capitalize cap, CharSequence cs, int off)117     public static boolean shouldCap(Capitalize cap, CharSequence cs, int off) {
118         int i;
119         char c;
120 
121         if (cap == Capitalize.NONE) {
122             return false;
123         }
124         if (cap == Capitalize.CHARACTERS) {
125             return true;
126         }
127 
128         return TextUtils.getCapsMode(cs, off, cap == Capitalize.WORDS
129                 ? TextUtils.CAP_MODE_WORDS : TextUtils.CAP_MODE_SENTENCES)
130                 != 0;
131     }
132 
getInputType()133     public int getInputType() {
134         return makeTextContentType(mAutoCap, mAutoText);
135     }
136 
137     @Override
onKeyDown(View view, Editable content, int keyCode, KeyEvent event)138     public boolean onKeyDown(View view, Editable content,
139                              int keyCode, KeyEvent event) {
140         KeyListener im = getKeyListener(event);
141 
142         return im.onKeyDown(view, content, keyCode, event);
143     }
144 
145     @Override
onKeyUp(View view, Editable content, int keyCode, KeyEvent event)146     public boolean onKeyUp(View view, Editable content,
147                            int keyCode, KeyEvent event) {
148         KeyListener im = getKeyListener(event);
149 
150         return im.onKeyUp(view, content, keyCode, event);
151     }
152 
153     @Override
onKeyOther(View view, Editable content, KeyEvent event)154     public boolean onKeyOther(View view, Editable content, KeyEvent event) {
155         KeyListener im = getKeyListener(event);
156 
157         return im.onKeyOther(view, content, event);
158     }
159 
160     /**
161      * Clear all the input state (autotext, autocap, multitap, undo)
162      * from the specified Editable, going beyond Editable.clear(), which
163      * just clears the text but not the input state.
164      *
165      * @param e the buffer whose text and state are to be cleared.
166      */
clear(Editable e)167     public static void clear(Editable e) {
168         e.clear();
169         e.removeSpan(ACTIVE);
170         e.removeSpan(CAPPED);
171         e.removeSpan(INHIBIT_REPLACEMENT);
172         e.removeSpan(LAST_TYPED);
173 
174         QwertyKeyListener.Replaced[] repl = e.getSpans(0, e.length(),
175                                    QwertyKeyListener.Replaced.class);
176         final int count = repl.length;
177         for (int i = 0; i < count; i++) {
178             e.removeSpan(repl[i]);
179         }
180     }
181 
onSpanAdded(Spannable s, Object what, int start, int end)182     public void onSpanAdded(Spannable s, Object what, int start, int end) { }
onSpanRemoved(Spannable s, Object what, int start, int end)183     public void onSpanRemoved(Spannable s, Object what, int start, int end) { }
184 
onSpanChanged(Spannable s, Object what, int start, int end, int st, int en)185     public void onSpanChanged(Spannable s, Object what, int start, int end,
186                               int st, int en) {
187         if (what == Selection.SELECTION_END) {
188             s.removeSpan(ACTIVE);
189         }
190     }
191 
getKeyListener(KeyEvent event)192     private KeyListener getKeyListener(KeyEvent event) {
193         KeyCharacterMap kmap = event.getKeyCharacterMap();
194         int kind = kmap.getKeyboardType();
195 
196         if (kind == KeyCharacterMap.ALPHA) {
197             return QwertyKeyListener.getInstance(mAutoText, mAutoCap);
198         } else if (kind == KeyCharacterMap.NUMERIC) {
199             return MultiTapKeyListener.getInstance(mAutoText, mAutoCap);
200         } else if (kind == KeyCharacterMap.FULL
201                 || kind == KeyCharacterMap.SPECIAL_FUNCTION) {
202             // We consider special function keyboards full keyboards as a workaround for
203             // devices that do not have built-in keyboards.  Applications may try to inject
204             // key events using the built-in keyboard device id which may be configured as
205             // a special function keyboard using a default key map.  Ideally, as of Honeycomb,
206             // these applications should be modified to use KeyCharacterMap.VIRTUAL_KEYBOARD.
207             return QwertyKeyListener.getInstanceForFullKeyboard();
208         }
209 
210         return NullKeyListener.getInstance();
211     }
212 
213     public enum Capitalize {
214         NONE, SENTENCES, WORDS, CHARACTERS,
215     }
216 
217     private static class NullKeyListener implements KeyListener
218     {
getInputType()219         public int getInputType() {
220             return InputType.TYPE_NULL;
221         }
222 
onKeyDown(View view, Editable content, int keyCode, KeyEvent event)223         public boolean onKeyDown(View view, Editable content,
224                                  int keyCode, KeyEvent event) {
225             return false;
226         }
227 
onKeyUp(View view, Editable content, int keyCode, KeyEvent event)228         public boolean onKeyUp(View view, Editable content, int keyCode,
229                                         KeyEvent event) {
230             return false;
231         }
232 
onKeyOther(View view, Editable content, KeyEvent event)233         public boolean onKeyOther(View view, Editable content, KeyEvent event) {
234             return false;
235         }
236 
clearMetaKeyState(View view, Editable content, int states)237         public void clearMetaKeyState(View view, Editable content, int states) {
238         }
239 
getInstance()240         public static NullKeyListener getInstance() {
241             if (sInstance != null)
242                 return sInstance;
243 
244             sInstance = new NullKeyListener();
245             return sInstance;
246         }
247 
248         private static NullKeyListener sInstance;
249     }
250 
release()251     public void release() {
252         if (mResolver != null) {
253             final ContentResolver contentResolver = mResolver.get();
254             if (contentResolver != null) {
255                 contentResolver.unregisterContentObserver(mObserver);
256                 mResolver.clear();
257             }
258             mObserver = null;
259             mResolver = null;
260             mPrefsInited = false;
261         }
262     }
263 
initPrefs(Context context)264     private void initPrefs(Context context) {
265         final ContentResolver contentResolver = context.getContentResolver();
266         mResolver = new WeakReference<ContentResolver>(contentResolver);
267         if (mObserver == null) {
268             mObserver = new SettingsObserver();
269             contentResolver.registerContentObserver(Settings.System.CONTENT_URI, true, mObserver);
270         }
271 
272         updatePrefs(contentResolver);
273         mPrefsInited = true;
274     }
275 
276     private class SettingsObserver extends ContentObserver {
SettingsObserver()277         public SettingsObserver() {
278             super(new Handler());
279         }
280 
281         @Override
onChange(boolean selfChange)282         public void onChange(boolean selfChange) {
283             if (mResolver != null) {
284                 final ContentResolver contentResolver = mResolver.get();
285                 if (contentResolver == null) {
286                     mPrefsInited = false;
287                 } else {
288                     updatePrefs(contentResolver);
289                 }
290             } else {
291                 mPrefsInited = false;
292             }
293         }
294     }
295 
updatePrefs(ContentResolver resolver)296     private void updatePrefs(ContentResolver resolver) {
297         boolean cap = System.getInt(resolver, System.TEXT_AUTO_CAPS, 1) > 0;
298         boolean text = System.getInt(resolver, System.TEXT_AUTO_REPLACE, 1) > 0;
299         boolean period = System.getInt(resolver, System.TEXT_AUTO_PUNCTUATE, 1) > 0;
300         boolean pw = System.getInt(resolver, System.TEXT_SHOW_PASSWORD, 1) > 0;
301 
302         mPrefs = (cap ? AUTO_CAP : 0) |
303                  (text ? AUTO_TEXT : 0) |
304                  (period ? AUTO_PERIOD : 0) |
305                  (pw ? SHOW_PASSWORD : 0);
306     }
307 
getPrefs(Context context)308     /* package */ int getPrefs(Context context) {
309         synchronized (this) {
310             if (!mPrefsInited || mResolver.refersTo(null)) {
311                 initPrefs(context);
312             }
313         }
314 
315         return mPrefs;
316     }
317 }
318