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