• 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.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