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 */ 37 public 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 */ TextKeyListener(Capitalize cap, boolean autotext)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 */ getInstance(boolean autotext, Capitalize cap)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 */ getInstance()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 */ shouldCap(Capitalize cap, CharSequence cs, int off)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 getInputType()123 public int getInputType() { 124 return makeTextContentType(mAutoCap, mAutoText); 125 } 126 127 @Override onKeyDown(View view, Editable content, int keyCode, KeyEvent event)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 onKeyUp(View view, Editable content, int keyCode, KeyEvent event)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 onKeyOther(View view, Editable content, KeyEvent event)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 */ clear(Editable e)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 onSpanAdded(Spannable s, Object what, int start, int end)172 public void onSpanAdded(Spannable s, Object what, int start, int end) { } onSpanRemoved(Spannable s, Object what, int start, int end)173 public void onSpanRemoved(Spannable s, Object what, int start, int end) { } 174 onSpanChanged(Spannable s, Object what, int start, int end, int st, int en)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 getKeyListener(KeyEvent event)182 private KeyListener getKeyListener(KeyEvent event) { 183 KeyCharacterMap kmap = event.getKeyCharacterMap(); 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 } else if (kind == KeyCharacterMap.FULL 191 || kind == KeyCharacterMap.SPECIAL_FUNCTION) { 192 // We consider special function keyboards full keyboards as a workaround for 193 // devices that do not have built-in keyboards. Applications may try to inject 194 // key events using the built-in keyboard device id which may be configured as 195 // a special function keyboard using a default key map. Ideally, as of Honeycomb, 196 // these applications should be modified to use KeyCharacterMap.VIRTUAL_KEYBOARD. 197 return QwertyKeyListener.getInstanceForFullKeyboard(); 198 } 199 200 return NullKeyListener.getInstance(); 201 } 202 203 public enum Capitalize { 204 NONE, SENTENCES, WORDS, CHARACTERS, 205 } 206 207 private static class NullKeyListener implements KeyListener 208 { getInputType()209 public int getInputType() { 210 return InputType.TYPE_NULL; 211 } 212 onKeyDown(View view, Editable content, int keyCode, KeyEvent event)213 public boolean onKeyDown(View view, Editable content, 214 int keyCode, KeyEvent event) { 215 return false; 216 } 217 onKeyUp(View view, Editable content, int keyCode, KeyEvent event)218 public boolean onKeyUp(View view, Editable content, int keyCode, 219 KeyEvent event) { 220 return false; 221 } 222 onKeyOther(View view, Editable content, KeyEvent event)223 public boolean onKeyOther(View view, Editable content, KeyEvent event) { 224 return false; 225 } 226 clearMetaKeyState(View view, Editable content, int states)227 public void clearMetaKeyState(View view, Editable content, int states) { 228 } 229 getInstance()230 public static NullKeyListener getInstance() { 231 if (sInstance != null) 232 return sInstance; 233 234 sInstance = new NullKeyListener(); 235 return sInstance; 236 } 237 238 private static NullKeyListener sInstance; 239 } 240 release()241 public void release() { 242 if (mResolver != null) { 243 final ContentResolver contentResolver = mResolver.get(); 244 if (contentResolver != null) { 245 contentResolver.unregisterContentObserver(mObserver); 246 mResolver.clear(); 247 } 248 mObserver = null; 249 mResolver = null; 250 mPrefsInited = false; 251 } 252 } 253 initPrefs(Context context)254 private void initPrefs(Context context) { 255 final ContentResolver contentResolver = context.getContentResolver(); 256 mResolver = new WeakReference<ContentResolver>(contentResolver); 257 if (mObserver == null) { 258 mObserver = new SettingsObserver(); 259 contentResolver.registerContentObserver(Settings.System.CONTENT_URI, true, mObserver); 260 } 261 262 updatePrefs(contentResolver); 263 mPrefsInited = true; 264 } 265 266 private class SettingsObserver extends ContentObserver { SettingsObserver()267 public SettingsObserver() { 268 super(new Handler()); 269 } 270 271 @Override onChange(boolean selfChange)272 public void onChange(boolean selfChange) { 273 if (mResolver != null) { 274 final ContentResolver contentResolver = mResolver.get(); 275 if (contentResolver == null) { 276 mPrefsInited = false; 277 } else { 278 updatePrefs(contentResolver); 279 } 280 } else { 281 mPrefsInited = false; 282 } 283 } 284 } 285 updatePrefs(ContentResolver resolver)286 private void updatePrefs(ContentResolver resolver) { 287 boolean cap = System.getInt(resolver, System.TEXT_AUTO_CAPS, 1) > 0; 288 boolean text = System.getInt(resolver, System.TEXT_AUTO_REPLACE, 1) > 0; 289 boolean period = System.getInt(resolver, System.TEXT_AUTO_PUNCTUATE, 1) > 0; 290 boolean pw = System.getInt(resolver, System.TEXT_SHOW_PASSWORD, 1) > 0; 291 292 mPrefs = (cap ? AUTO_CAP : 0) | 293 (text ? AUTO_TEXT : 0) | 294 (period ? AUTO_PERIOD : 0) | 295 (pw ? SHOW_PASSWORD : 0); 296 } 297 getPrefs(Context context)298 /* package */ int getPrefs(Context context) { 299 synchronized (this) { 300 if (!mPrefsInited || mResolver.get() == null) { 301 initPrefs(context); 302 } 303 } 304 305 return mPrefs; 306 } 307 } 308