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