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.Editable; 26 import android.text.InputType; 27 import android.text.NoCopySpan; 28 import android.text.Selection; 29 import android.text.SpanWatcher; 30 import android.text.Spannable; 31 import android.text.TextUtils; 32 import android.view.KeyCharacterMap; 33 import android.view.KeyEvent; 34 import android.view.View; 35 36 import java.lang.ref.WeakReference; 37 38 /** 39 * This is the key listener for typing normal text. It delegates to 40 * other key listeners appropriate to the current keyboard and language. 41 * <p></p> 42 * As for all implementations of {@link KeyListener}, this class is only concerned 43 * with hardware keyboards. Software input methods have no obligation to trigger 44 * the methods in this class. 45 */ 46 public class TextKeyListener extends BaseKeyListener implements SpanWatcher { 47 private static TextKeyListener[] sInstance = 48 new TextKeyListener[Capitalize.values().length * 2]; 49 50 /* package */ static final Object ACTIVE = new NoCopySpan.Concrete(); 51 /* package */ static final Object CAPPED = new NoCopySpan.Concrete(); 52 /* package */ static final Object INHIBIT_REPLACEMENT = new NoCopySpan.Concrete(); 53 /* package */ static final Object LAST_TYPED = new NoCopySpan.Concrete(); 54 55 private Capitalize mAutoCap; 56 private boolean mAutoText; 57 58 private int mPrefs; 59 private boolean mPrefsInited; 60 61 /* package */ static final int AUTO_CAP = 1; 62 /* package */ static final int AUTO_TEXT = 2; 63 /* package */ static final int AUTO_PERIOD = 4; 64 /* package */ static final int SHOW_PASSWORD = 8; 65 private WeakReference<ContentResolver> mResolver; 66 private TextKeyListener.SettingsObserver mObserver; 67 68 /** 69 * Creates a new TextKeyListener with the specified capitalization 70 * and correction properties. 71 * 72 * @param cap when, if ever, to automatically capitalize. 73 * @param autotext whether to automatically do spelling corrections. 74 */ TextKeyListener(Capitalize cap, boolean autotext)75 public TextKeyListener(Capitalize cap, boolean autotext) { 76 mAutoCap = cap; 77 mAutoText = autotext; 78 } 79 80 /** 81 * Returns a new or existing instance with the specified capitalization 82 * and correction properties. 83 * 84 * @param cap when, if ever, to automatically capitalize. 85 * @param autotext whether to automatically do spelling corrections. 86 */ getInstance(boolean autotext, Capitalize cap)87 public static TextKeyListener getInstance(boolean autotext, 88 Capitalize cap) { 89 int off = cap.ordinal() * 2 + (autotext ? 1 : 0); 90 91 if (sInstance[off] == null) { 92 sInstance[off] = new TextKeyListener(cap, autotext); 93 } 94 95 return sInstance[off]; 96 } 97 98 /** 99 * Returns a new or existing instance with no automatic capitalization 100 * or correction. 101 */ getInstance()102 public static TextKeyListener getInstance() { 103 return getInstance(false, Capitalize.NONE); 104 } 105 106 /** 107 * Returns whether it makes sense to automatically capitalize at the 108 * specified position in the specified text, with the specified rules. 109 * 110 * @param cap the capitalization rules to consider. 111 * @param cs the text in which an insertion is being made. 112 * @param off the offset into that text where the insertion is being made. 113 * 114 * @return whether the character being inserted should be capitalized. 115 */ shouldCap(Capitalize cap, CharSequence cs, int off)116 public static boolean shouldCap(Capitalize cap, CharSequence cs, int off) { 117 int i; 118 char c; 119 120 if (cap == Capitalize.NONE) { 121 return false; 122 } 123 if (cap == Capitalize.CHARACTERS) { 124 return true; 125 } 126 127 return TextUtils.getCapsMode(cs, off, cap == Capitalize.WORDS 128 ? TextUtils.CAP_MODE_WORDS : TextUtils.CAP_MODE_SENTENCES) 129 != 0; 130 } 131 getInputType()132 public int getInputType() { 133 return makeTextContentType(mAutoCap, mAutoText); 134 } 135 136 @Override onKeyDown(View view, Editable content, int keyCode, KeyEvent event)137 public boolean onKeyDown(View view, Editable content, 138 int keyCode, KeyEvent event) { 139 KeyListener im = getKeyListener(event); 140 141 return im.onKeyDown(view, content, keyCode, event); 142 } 143 144 @Override onKeyUp(View view, Editable content, int keyCode, KeyEvent event)145 public boolean onKeyUp(View view, Editable content, 146 int keyCode, KeyEvent event) { 147 KeyListener im = getKeyListener(event); 148 149 return im.onKeyUp(view, content, keyCode, event); 150 } 151 152 @Override onKeyOther(View view, Editable content, KeyEvent event)153 public boolean onKeyOther(View view, Editable content, KeyEvent event) { 154 KeyListener im = getKeyListener(event); 155 156 return im.onKeyOther(view, content, event); 157 } 158 159 /** 160 * Clear all the input state (autotext, autocap, multitap, undo) 161 * from the specified Editable, going beyond Editable.clear(), which 162 * just clears the text but not the input state. 163 * 164 * @param e the buffer whose text and state are to be cleared. 165 */ clear(Editable e)166 public static void clear(Editable e) { 167 e.clear(); 168 e.removeSpan(ACTIVE); 169 e.removeSpan(CAPPED); 170 e.removeSpan(INHIBIT_REPLACEMENT); 171 e.removeSpan(LAST_TYPED); 172 173 QwertyKeyListener.Replaced[] repl = e.getSpans(0, e.length(), 174 QwertyKeyListener.Replaced.class); 175 final int count = repl.length; 176 for (int i = 0; i < count; i++) { 177 e.removeSpan(repl[i]); 178 } 179 } 180 onSpanAdded(Spannable s, Object what, int start, int end)181 public void onSpanAdded(Spannable s, Object what, int start, int end) { } onSpanRemoved(Spannable s, Object what, int start, int end)182 public void onSpanRemoved(Spannable s, Object what, int start, int end) { } 183 onSpanChanged(Spannable s, Object what, int start, int end, int st, int en)184 public void onSpanChanged(Spannable s, Object what, int start, int end, 185 int st, int en) { 186 if (what == Selection.SELECTION_END) { 187 s.removeSpan(ACTIVE); 188 } 189 } 190 getKeyListener(KeyEvent event)191 private KeyListener getKeyListener(KeyEvent event) { 192 KeyCharacterMap kmap = event.getKeyCharacterMap(); 193 int kind = kmap.getKeyboardType(); 194 195 if (kind == KeyCharacterMap.ALPHA) { 196 return QwertyKeyListener.getInstance(mAutoText, mAutoCap); 197 } else if (kind == KeyCharacterMap.NUMERIC) { 198 return MultiTapKeyListener.getInstance(mAutoText, mAutoCap); 199 } else if (kind == KeyCharacterMap.FULL 200 || kind == KeyCharacterMap.SPECIAL_FUNCTION) { 201 // We consider special function keyboards full keyboards as a workaround for 202 // devices that do not have built-in keyboards. Applications may try to inject 203 // key events using the built-in keyboard device id which may be configured as 204 // a special function keyboard using a default key map. Ideally, as of Honeycomb, 205 // these applications should be modified to use KeyCharacterMap.VIRTUAL_KEYBOARD. 206 return QwertyKeyListener.getInstanceForFullKeyboard(); 207 } 208 209 return NullKeyListener.getInstance(); 210 } 211 212 public enum Capitalize { 213 NONE, SENTENCES, WORDS, CHARACTERS, 214 } 215 216 private static class NullKeyListener implements KeyListener 217 { getInputType()218 public int getInputType() { 219 return InputType.TYPE_NULL; 220 } 221 onKeyDown(View view, Editable content, int keyCode, KeyEvent event)222 public boolean onKeyDown(View view, Editable content, 223 int keyCode, KeyEvent event) { 224 return false; 225 } 226 onKeyUp(View view, Editable content, int keyCode, KeyEvent event)227 public boolean onKeyUp(View view, Editable content, int keyCode, 228 KeyEvent event) { 229 return false; 230 } 231 onKeyOther(View view, Editable content, KeyEvent event)232 public boolean onKeyOther(View view, Editable content, KeyEvent event) { 233 return false; 234 } 235 clearMetaKeyState(View view, Editable content, int states)236 public void clearMetaKeyState(View view, Editable content, int states) { 237 } 238 getInstance()239 public static NullKeyListener getInstance() { 240 if (sInstance != null) 241 return sInstance; 242 243 sInstance = new NullKeyListener(); 244 return sInstance; 245 } 246 247 private static NullKeyListener sInstance; 248 } 249 release()250 public void release() { 251 if (mResolver != null) { 252 final ContentResolver contentResolver = mResolver.get(); 253 if (contentResolver != null) { 254 contentResolver.unregisterContentObserver(mObserver); 255 mResolver.clear(); 256 } 257 mObserver = null; 258 mResolver = null; 259 mPrefsInited = false; 260 } 261 } 262 initPrefs(Context context)263 private void initPrefs(Context context) { 264 final ContentResolver contentResolver = context.getContentResolver(); 265 mResolver = new WeakReference<ContentResolver>(contentResolver); 266 if (mObserver == null) { 267 mObserver = new SettingsObserver(); 268 contentResolver.registerContentObserver(Settings.System.CONTENT_URI, true, mObserver); 269 } 270 271 updatePrefs(contentResolver); 272 mPrefsInited = true; 273 } 274 275 private class SettingsObserver extends ContentObserver { SettingsObserver()276 public SettingsObserver() { 277 super(new Handler()); 278 } 279 280 @Override onChange(boolean selfChange)281 public void onChange(boolean selfChange) { 282 if (mResolver != null) { 283 final ContentResolver contentResolver = mResolver.get(); 284 if (contentResolver == null) { 285 mPrefsInited = false; 286 } else { 287 updatePrefs(contentResolver); 288 } 289 } else { 290 mPrefsInited = false; 291 } 292 } 293 } 294 updatePrefs(ContentResolver resolver)295 private void updatePrefs(ContentResolver resolver) { 296 boolean cap = System.getInt(resolver, System.TEXT_AUTO_CAPS, 1) > 0; 297 boolean text = System.getInt(resolver, System.TEXT_AUTO_REPLACE, 1) > 0; 298 boolean period = System.getInt(resolver, System.TEXT_AUTO_PUNCTUATE, 1) > 0; 299 boolean pw = System.getInt(resolver, System.TEXT_SHOW_PASSWORD, 1) > 0; 300 301 mPrefs = (cap ? AUTO_CAP : 0) | 302 (text ? AUTO_TEXT : 0) | 303 (period ? AUTO_PERIOD : 0) | 304 (pw ? SHOW_PASSWORD : 0); 305 } 306 getPrefs(Context context)307 /* package */ int getPrefs(Context context) { 308 synchronized (this) { 309 if (!mPrefsInited || mResolver.get() == null) { 310 initPrefs(context); 311 } 312 } 313 314 return mPrefs; 315 } 316 } 317