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