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