TextKeyListener.java revision f013e1afd1e68af5e3b868c26a653bbfb39538f8
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;
29import android.text.InputType;
30
31import 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 */
37public 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 Object();
42    /* package */ static final Object CAPPED = new Object();
43    /* package */ static final Object INHIBIT_REPLACEMENT = new Object();
44    /* package */ static final Object LAST_TYPED = new Object();
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     */
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     */
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     */
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     */
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
123    public int getInputType() {
124        return makeTextContentType(mAutoCap, mAutoText);
125    }
126
127    @Override
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
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    /**
144     * Clear all the input state (autotext, autocap, multitap, undo)
145     * from the specified Editable, going beyond Editable.clear(), which
146     * just clears the text but not the input state.
147     *
148     * @param e the buffer whose text and state are to be cleared.
149     */
150    public static void clear(Editable e) {
151        e.clear();
152        e.removeSpan(ACTIVE);
153        e.removeSpan(CAPPED);
154        e.removeSpan(INHIBIT_REPLACEMENT);
155        e.removeSpan(LAST_TYPED);
156
157        QwertyKeyListener.Replaced[] repl = e.getSpans(0, e.length(),
158                                   QwertyKeyListener.Replaced.class);
159        final int count = repl.length;
160        for (int i = 0; i < count; i++) {
161            e.removeSpan(repl[i]);
162        }
163    }
164
165    public void onSpanAdded(Spannable s, Object what, int start, int end) { }
166    public void onSpanRemoved(Spannable s, Object what, int start, int end) { }
167
168    public void onSpanChanged(Spannable s, Object what, int start, int end,
169                              int st, int en) {
170        if (what == Selection.SELECTION_END) {
171            s.removeSpan(ACTIVE);
172        }
173    }
174
175    private KeyListener getKeyListener(KeyEvent event) {
176        KeyCharacterMap kmap = KeyCharacterMap.load(event.getKeyboardDevice());
177        int kind = kmap.getKeyboardType();
178
179        if (kind == KeyCharacterMap.ALPHA) {
180            return QwertyKeyListener.getInstance(mAutoText, mAutoCap);
181        } else if (kind == KeyCharacterMap.NUMERIC) {
182            return MultiTapKeyListener.getInstance(mAutoText, mAutoCap);
183        }
184
185        return NullKeyListener.getInstance();
186    }
187
188    public enum Capitalize {
189        NONE, SENTENCES, WORDS, CHARACTERS,
190    }
191
192    private static class NullKeyListener implements KeyListener
193    {
194        public int getInputType() {
195            return InputType.TYPE_NULL;
196        }
197
198        public boolean onKeyDown(View view, Editable content,
199                                 int keyCode, KeyEvent event) {
200            return false;
201        }
202
203        public boolean onKeyUp(View view, Editable content, int keyCode,
204                                        KeyEvent event) {
205            return false;
206        }
207
208        public void clearMetaKeyState(View view, Editable content, int states) {
209        }
210
211        public static NullKeyListener getInstance() {
212            if (sInstance != null)
213                return sInstance;
214
215            sInstance = new NullKeyListener();
216            return sInstance;
217        }
218
219        private static NullKeyListener sInstance;
220    }
221
222    public void release() {
223        if (mResolver != null) {
224            final ContentResolver contentResolver = mResolver.get();
225            if (contentResolver != null) {
226                contentResolver.unregisterContentObserver(mObserver);
227                mResolver.clear();
228            }
229            mObserver = null;
230            mResolver = null;
231            mPrefsInited = false;
232        }
233    }
234
235    private void initPrefs(Context context) {
236        final ContentResolver contentResolver = context.getContentResolver();
237        mResolver = new WeakReference<ContentResolver>(contentResolver);
238        mObserver = new SettingsObserver();
239        contentResolver.registerContentObserver(Settings.System.CONTENT_URI, true, mObserver);
240
241        updatePrefs(contentResolver);
242        mPrefsInited = true;
243    }
244
245    private class SettingsObserver extends ContentObserver {
246        public SettingsObserver() {
247            super(new Handler());
248        }
249
250        @Override
251        public void onChange(boolean selfChange) {
252            if (mResolver != null) {
253                final ContentResolver contentResolver = mResolver.get();
254                if (contentResolver == null) {
255                    mPrefsInited = false;
256                } else {
257                    updatePrefs(contentResolver);
258                }
259            } else {
260                mPrefsInited = false;
261            }
262        }
263    }
264
265    private void updatePrefs(ContentResolver resolver) {
266        boolean cap = System.getInt(resolver, System.TEXT_AUTO_CAPS, 1) > 0;
267        boolean text = System.getInt(resolver, System.TEXT_AUTO_REPLACE, 1) > 0;
268        boolean period = System.getInt(resolver, System.TEXT_AUTO_PUNCTUATE, 1) > 0;
269        boolean pw = System.getInt(resolver, System.TEXT_SHOW_PASSWORD, 1) > 0;
270
271        mPrefs = (cap ? AUTO_CAP : 0) |
272                 (text ? AUTO_TEXT : 0) |
273                 (period ? AUTO_PERIOD : 0) |
274                 (pw ? SHOW_PASSWORD : 0);
275    }
276
277    /* package */ int getPrefs(Context context) {
278        synchronized (this) {
279            if (!mPrefsInited || mResolver.get() == null) {
280                initPrefs(context);
281            }
282        }
283
284        return mPrefs;
285    }
286}
287