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