TextKeyListener.java revision 4037d51b132a85dcfe37a95f9d2d91ad23d162fd
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
17package android.text.method;
18
19import android.content.ContentResolver;
20import android.content.Context;
21import android.database.ContentObserver;
22import android.os.Handler;
23import android.provider.Settings;
24import android.provider.Settings.System;
25import android.text.Editable;
26import android.text.InputType;
27import android.text.NoCopySpan;
28import android.text.Selection;
29import android.text.SpanWatcher;
30import android.text.Spannable;
31import android.text.TextUtils;
32import android.view.KeyCharacterMap;
33import android.view.KeyEvent;
34import android.view.View;
35
36import 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 */
46public 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     */
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     */
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     */
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     */
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
132    public int getInputType() {
133        return makeTextContentType(mAutoCap, mAutoText);
134    }
135
136    @Override
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
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
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     */
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
181    public void onSpanAdded(Spannable s, Object what, int start, int end) { }
182    public void onSpanRemoved(Spannable s, Object what, int start, int end) { }
183
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
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    {
218        public int getInputType() {
219            return InputType.TYPE_NULL;
220        }
221
222        public boolean onKeyDown(View view, Editable content,
223                                 int keyCode, KeyEvent event) {
224            return false;
225        }
226
227        public boolean onKeyUp(View view, Editable content, int keyCode,
228                                        KeyEvent event) {
229            return false;
230        }
231
232        public boolean onKeyOther(View view, Editable content, KeyEvent event) {
233            return false;
234        }
235
236        public void clearMetaKeyState(View view, Editable content, int states) {
237        }
238
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
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
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 {
276        public SettingsObserver() {
277            super(new Handler());
278        }
279
280        @Override
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
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
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