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