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 17 package android.text.method; 18 19 import android.content.ContentResolver; 20 import android.content.Context; 21 import android.database.ContentObserver; 22 import android.os.Handler; 23 import android.provider.Settings; 24 import android.provider.Settings.System; 25 import android.text.*; 26 import android.view.KeyCharacterMap; 27 import android.view.KeyEvent; 28 import android.view.View; 29 import android.text.InputType; 30 31 import 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 */ 41 public 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