1 /* 2 * Copyright (C) 2006 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.view.KeyEvent; 20 import android.view.View; 21 import android.os.Handler; 22 import android.os.SystemClock; 23 import android.text.*; 24 import android.text.method.TextKeyListener.Capitalize; 25 import android.util.SparseArray; 26 27 /** 28 * This is the standard key listener for alphabetic input on 12-key 29 * keyboards. You should generally not need to instantiate this yourself; 30 * TextKeyListener will do it for you. 31 * <p></p> 32 * As for all implementations of {@link KeyListener}, this class is only concerned 33 * with hardware keyboards. Software input methods have no obligation to trigger 34 * the methods in this class. 35 */ 36 public class MultiTapKeyListener extends BaseKeyListener 37 implements SpanWatcher { 38 private static MultiTapKeyListener[] sInstance = 39 new MultiTapKeyListener[Capitalize.values().length * 2]; 40 41 private static final SparseArray<String> sRecs = new SparseArray<String>(); 42 43 private Capitalize mCapitalize; 44 private boolean mAutoText; 45 46 static { 47 sRecs.put(KeyEvent.KEYCODE_1, ".,1!@#$%^&*:/?'=()"); 48 sRecs.put(KeyEvent.KEYCODE_2, "abc2ABC"); 49 sRecs.put(KeyEvent.KEYCODE_3, "def3DEF"); 50 sRecs.put(KeyEvent.KEYCODE_4, "ghi4GHI"); 51 sRecs.put(KeyEvent.KEYCODE_5, "jkl5JKL"); 52 sRecs.put(KeyEvent.KEYCODE_6, "mno6MNO"); 53 sRecs.put(KeyEvent.KEYCODE_7, "pqrs7PQRS"); 54 sRecs.put(KeyEvent.KEYCODE_8, "tuv8TUV"); 55 sRecs.put(KeyEvent.KEYCODE_9, "wxyz9WXYZ"); 56 sRecs.put(KeyEvent.KEYCODE_0, "0+"); 57 sRecs.put(KeyEvent.KEYCODE_POUND, " "); 58 }; 59 60 public MultiTapKeyListener(Capitalize cap, 61 boolean autotext) { 62 mCapitalize = cap; 63 mAutoText = autotext; 64 } 65 66 /** 67 * Returns a new or existing instance with the specified capitalization 68 * and correction properties. 69 */ 70 public static MultiTapKeyListener getInstance(boolean autotext, 71 Capitalize cap) { 72 int off = cap.ordinal() * 2 + (autotext ? 1 : 0); 73 74 if (sInstance[off] == null) { 75 sInstance[off] = new MultiTapKeyListener(cap, autotext); 76 } 77 78 return sInstance[off]; 79 } 80 81 public int getInputType() { 82 return makeTextContentType(mCapitalize, mAutoText); 83 } 84 85 public boolean onKeyDown(View view, Editable content, 86 int keyCode, KeyEvent event) { 87 int selStart, selEnd; 88 int pref = 0; 89 90 if (view != null) { 91 pref = TextKeyListener.getInstance().getPrefs(view.getContext()); 92 } 93 94 { 95 int a = Selection.getSelectionStart(content); 96 int b = Selection.getSelectionEnd(content); 97 98 selStart = Math.min(a, b); 99 selEnd = Math.max(a, b); 100 } 101 102 int activeStart = content.getSpanStart(TextKeyListener.ACTIVE); 103 int activeEnd = content.getSpanEnd(TextKeyListener.ACTIVE); 104 105 // now for the multitap cases... 106 107 // Try to increment the character we were working on before 108 // if we have one and it's still the same key. 109 110 int rec = (content.getSpanFlags(TextKeyListener.ACTIVE) 111 & Spannable.SPAN_USER) >>> Spannable.SPAN_USER_SHIFT; 112 113 if (activeStart == selStart && activeEnd == selEnd && 114 selEnd - selStart == 1 && 115 rec >= 0 && rec < sRecs.size()) { 116 if (keyCode == KeyEvent.KEYCODE_STAR) { 117 char current = content.charAt(selStart); 118 119 if (Character.isLowerCase(current)) { 120 content.replace(selStart, selEnd, 121 String.valueOf(current).toUpperCase()); 122 removeTimeouts(content); 123 new Timeout(content); // for its side effects 124 125 return true; 126 } 127 if (Character.isUpperCase(current)) { 128 content.replace(selStart, selEnd, 129 String.valueOf(current).toLowerCase()); 130 removeTimeouts(content); 131 new Timeout(content); // for its side effects 132 133 return true; 134 } 135 } 136 137 if (sRecs.indexOfKey(keyCode) == rec) { 138 String val = sRecs.valueAt(rec); 139 char ch = content.charAt(selStart); 140 int ix = val.indexOf(ch); 141 142 if (ix >= 0) { 143 ix = (ix + 1) % (val.length()); 144 145 content.replace(selStart, selEnd, val, ix, ix + 1); 146 removeTimeouts(content); 147 new Timeout(content); // for its side effects 148 149 return true; 150 } 151 } 152 153 // Is this key one we know about at all? If so, acknowledge 154 // that the selection is our fault but the key has changed 155 // or the text no longer matches, so move the selection over 156 // so that it inserts instead of replaces. 157 158 rec = sRecs.indexOfKey(keyCode); 159 160 if (rec >= 0) { 161 Selection.setSelection(content, selEnd, selEnd); 162 selStart = selEnd; 163 } 164 } else { 165 rec = sRecs.indexOfKey(keyCode); 166 } 167 168 if (rec >= 0) { 169 // We have a valid key. Replace the selection or insertion point 170 // with the first character for that key, and remember what 171 // record it came from for next time. 172 173 String val = sRecs.valueAt(rec); 174 175 int off = 0; 176 if ((pref & TextKeyListener.AUTO_CAP) != 0 && 177 TextKeyListener.shouldCap(mCapitalize, content, selStart)) { 178 for (int i = 0; i < val.length(); i++) { 179 if (Character.isUpperCase(val.charAt(i))) { 180 off = i; 181 break; 182 } 183 } 184 } 185 186 if (selStart != selEnd) { 187 Selection.setSelection(content, selEnd); 188 } 189 190 content.setSpan(OLD_SEL_START, selStart, selStart, 191 Spannable.SPAN_MARK_MARK); 192 193 content.replace(selStart, selEnd, val, off, off + 1); 194 195 int oldStart = content.getSpanStart(OLD_SEL_START); 196 selEnd = Selection.getSelectionEnd(content); 197 198 if (selEnd != oldStart) { 199 Selection.setSelection(content, oldStart, selEnd); 200 201 content.setSpan(TextKeyListener.LAST_TYPED, 202 oldStart, selEnd, 203 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 204 205 content.setSpan(TextKeyListener.ACTIVE, 206 oldStart, selEnd, 207 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE | 208 (rec << Spannable.SPAN_USER_SHIFT)); 209 210 } 211 212 removeTimeouts(content); 213 new Timeout(content); // for its side effects 214 215 // Set up the callback so we can remove the timeout if the 216 // cursor moves. 217 218 if (content.getSpanStart(this) < 0) { 219 KeyListener[] methods = content.getSpans(0, content.length(), 220 KeyListener.class); 221 for (Object method : methods) { 222 content.removeSpan(method); 223 } 224 content.setSpan(this, 0, content.length(), 225 Spannable.SPAN_INCLUSIVE_INCLUSIVE); 226 } 227 228 return true; 229 } 230 231 return super.onKeyDown(view, content, keyCode, event); 232 } 233 234 public void onSpanChanged(Spannable buf, 235 Object what, int s, int e, int start, int stop) { 236 if (what == Selection.SELECTION_END) { 237 buf.removeSpan(TextKeyListener.ACTIVE); 238 removeTimeouts(buf); 239 } 240 } 241 242 private static void removeTimeouts(Spannable buf) { 243 Timeout[] timeout = buf.getSpans(0, buf.length(), Timeout.class); 244 245 for (int i = 0; i < timeout.length; i++) { 246 Timeout t = timeout[i]; 247 248 t.removeCallbacks(t); 249 t.mBuffer = null; 250 buf.removeSpan(t); 251 } 252 } 253 254 private class Timeout 255 extends Handler 256 implements Runnable 257 { 258 public Timeout(Editable buffer) { 259 mBuffer = buffer; 260 mBuffer.setSpan(Timeout.this, 0, mBuffer.length(), 261 Spannable.SPAN_INCLUSIVE_INCLUSIVE); 262 263 postAtTime(this, SystemClock.uptimeMillis() + 2000); 264 } 265 266 public void run() { 267 Spannable buf = mBuffer; 268 269 if (buf != null) { 270 int st = Selection.getSelectionStart(buf); 271 int en = Selection.getSelectionEnd(buf); 272 273 int start = buf.getSpanStart(TextKeyListener.ACTIVE); 274 int end = buf.getSpanEnd(TextKeyListener.ACTIVE); 275 276 if (st == start && en == end) { 277 Selection.setSelection(buf, Selection.getSelectionEnd(buf)); 278 } 279 280 buf.removeSpan(Timeout.this); 281 } 282 } 283 284 private Editable mBuffer; 285 } 286 287 public void onSpanAdded(Spannable s, Object what, int start, int end) { } 288 public void onSpanRemoved(Spannable s, Object what, int start, int end) { } 289 } 290 291