• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
sRecs.put(KeyEvent.KEYCODE_1, ".,1!@#$%^&*:/?'=()")47         sRecs.put(KeyEvent.KEYCODE_1,     ".,1!@#$%^&*:/?'=()");
sRecs.put(KeyEvent.KEYCODE_2, "abc2ABC")48         sRecs.put(KeyEvent.KEYCODE_2,     "abc2ABC");
sRecs.put(KeyEvent.KEYCODE_3, "def3DEF")49         sRecs.put(KeyEvent.KEYCODE_3,     "def3DEF");
sRecs.put(KeyEvent.KEYCODE_4, "ghi4GHI")50         sRecs.put(KeyEvent.KEYCODE_4,     "ghi4GHI");
sRecs.put(KeyEvent.KEYCODE_5, "jkl5JKL")51         sRecs.put(KeyEvent.KEYCODE_5,     "jkl5JKL");
sRecs.put(KeyEvent.KEYCODE_6, "mno6MNO")52         sRecs.put(KeyEvent.KEYCODE_6,     "mno6MNO");
sRecs.put(KeyEvent.KEYCODE_7, "pqrs7PQRS")53         sRecs.put(KeyEvent.KEYCODE_7,     "pqrs7PQRS");
sRecs.put(KeyEvent.KEYCODE_8, "tuv8TUV")54         sRecs.put(KeyEvent.KEYCODE_8,     "tuv8TUV");
sRecs.put(KeyEvent.KEYCODE_9, "wxyz9WXYZ")55         sRecs.put(KeyEvent.KEYCODE_9,     "wxyz9WXYZ");
sRecs.put(KeyEvent.KEYCODE_0, "0+")56         sRecs.put(KeyEvent.KEYCODE_0,     "0+");
sRecs.put(KeyEvent.KEYCODE_POUND, " ")57         sRecs.put(KeyEvent.KEYCODE_POUND, " ");
58     };
59 
MultiTapKeyListener(Capitalize cap, boolean autotext)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      */
getInstance(boolean autotext, Capitalize cap)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 
getInputType()81     public int getInputType() {
82         return makeTextContentType(mCapitalize, mAutoText);
83     }
84 
onKeyDown(View view, Editable content, int keyCode, KeyEvent event)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 
onSpanChanged(Spannable buf, Object what, int s, int e, int start, int stop)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 
removeTimeouts(Spannable buf)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     {
Timeout(Editable buffer)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 
run()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 
onSpanAdded(Spannable s, Object what, int start, int end)287     public void onSpanAdded(Spannable s, Object what, int start, int end) { }
onSpanRemoved(Spannable s, Object what, int start, int end)288     public void onSpanRemoved(Spannable s, Object what, int start, int end) { }
289 }
290 
291