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