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