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