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 * <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 */
41public 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     */
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     */
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     */
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     */
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
127    public int getInputType() {
128        return makeTextContentType(mAutoCap, mAutoText);
129    }
130
131    @Override
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
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
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     */
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
176    public void onSpanAdded(Spannable s, Object what, int start, int end) { }
177    public void onSpanRemoved(Spannable s, Object what, int start, int end) { }
178
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
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    {
213        public int getInputType() {
214            return InputType.TYPE_NULL;
215        }
216
217        public boolean onKeyDown(View view, Editable content,
218                                 int keyCode, KeyEvent event) {
219            return false;
220        }
221
222        public boolean onKeyUp(View view, Editable content, int keyCode,
223                                        KeyEvent event) {
224            return false;
225        }
226
227        public boolean onKeyOther(View view, Editable content, KeyEvent event) {
228            return false;
229        }
230
231        public void clearMetaKeyState(View view, Editable content, int states) {
232        }
233
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
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
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 {
271        public SettingsObserver() {
272            super(new Handler());
273        }
274
275        @Override
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
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
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