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