blob: 0a097f99bd0a635a60dd35f207a47bfa8e1bd69c [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
17package android.text.method;
18
Jeff Brown497a92c2010-09-12 17:55:08 -070019import android.text.Editable;
20import android.text.NoCopySpan;
21import android.text.Spannable;
22import android.text.Spanned;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.view.KeyEvent;
24import android.view.View;
Jeff Brown6b53e8d2010-11-10 16:03:06 -080025import android.view.KeyCharacterMap;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026
27/**
Jeff Brown6b53e8d2010-11-10 16:03:06 -080028 * This base class encapsulates the behavior for tracking the state of
29 * meta keys such as SHIFT, ALT and SYM as well as the pseudo-meta state of selecting text.
30 * <p>
31 * Key listeners that care about meta state should inherit from this class;
32 * you should not instantiate this class directly in a client.
33 * </p><p>
34 * This class provides two mechanisms for tracking meta state that can be used
35 * together or independently.
36 * </p>
37 * <ul>
38 * <li>Methods such as {@link #handleKeyDown(long, int, KeyEvent)} and
39 * {@link #getMetaState(long)} operate on a meta key state bit mask.</li>
40 * <li>Methods such as {@link #onKeyDown(View, Editable, int, KeyEvent)} and
41 * {@link #getMetaState(CharSequence, int)} operate on meta key state flags stored
42 * as spans in an {@link Editable} text buffer. The spans only describe the current
43 * meta key state of the text editor; they do not carry any positional information.</li>
44 * </ul>
45 * <p>
46 * The behavior of this class varies according to the keyboard capabilities
47 * described by the {@link KeyCharacterMap} of the keyboard device such as
48 * the {@link KeyCharacterMap#getModifierBehavior() key modifier behavior}.
49 * </p><p>
50 * {@link MetaKeyKeyListener} implements chorded and toggled key modifiers.
51 * When key modifiers are toggled into a latched or locked state, the state
52 * of the modifier is stored in the {@link Editable} text buffer or in a
53 * meta state integer managed by the client. These latched or locked modifiers
54 * should be considered to be held <b>in addition to</b> those that the
55 * keyboard already reported as being pressed in {@link KeyEvent#getMetaState()}.
56 * In other words, the {@link MetaKeyKeyListener} augments the meta state
57 * provided by the keyboard; it does not replace it. This distinction is important
58 * to ensure that meta keys not handled by {@link MetaKeyKeyListener} such as
59 * {@link KeyEvent#KEYCODE_CAPS_LOCK} or {@link KeyEvent#KEYCODE_NUM_LOCK} are
60 * taken into consideration.
61 * </p><p>
62 * To ensure correct meta key behavior, the following pattern should be used
63 * when mapping key codes to characters:
64 * </p>
65 * <code>
66 * private char getUnicodeChar(TextKeyListener listener, KeyEvent event, Editable textBuffer) {
67 * // Use the combined meta states from the event and the key listener.
68 * int metaState = event.getMetaState() | listener.getMetaState(textBuffer);
69 * return event.getUnicodeChar(metaState);
70 * }
71 * </code>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080072 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080073public abstract class MetaKeyKeyListener {
Jeff Brown497a92c2010-09-12 17:55:08 -070074 /**
75 * Flag that indicates that the SHIFT key is on.
76 * Value equals {@link KeyEvent#META_SHIFT_ON}.
77 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080078 public static final int META_SHIFT_ON = KeyEvent.META_SHIFT_ON;
Jeff Brown497a92c2010-09-12 17:55:08 -070079 /**
80 * Flag that indicates that the ALT key is on.
81 * Value equals {@link KeyEvent#META_ALT_ON}.
82 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080083 public static final int META_ALT_ON = KeyEvent.META_ALT_ON;
Jeff Brown497a92c2010-09-12 17:55:08 -070084 /**
85 * Flag that indicates that the SYM key is on.
86 * Value equals {@link KeyEvent#META_SYM_ON}.
87 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080088 public static final int META_SYM_ON = KeyEvent.META_SYM_ON;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080089
Jeff Brown497a92c2010-09-12 17:55:08 -070090 /**
91 * Flag that indicates that the SHIFT key is locked in CAPS mode.
92 */
93 public static final int META_CAP_LOCKED = KeyEvent.META_CAP_LOCKED;
94 /**
95 * Flag that indicates that the ALT key is locked.
96 */
97 public static final int META_ALT_LOCKED = KeyEvent.META_ALT_LOCKED;
98 /**
99 * Flag that indicates that the SYM key is locked.
100 */
101 public static final int META_SYM_LOCKED = KeyEvent.META_SYM_LOCKED;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800102
103 /**
104 * @hide pending API review
105 */
Jeff Brown497a92c2010-09-12 17:55:08 -0700106 public static final int META_SELECTING = KeyEvent.META_SELECTING;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800107
Jeff Brown497a92c2010-09-12 17:55:08 -0700108 // These bits are privately used by the meta key key listener.
109 // They are deliberately assigned values outside of the representable range of an 'int'
110 // so as not to conflict with any meta key states publicly defined by KeyEvent.
111 private static final long META_CAP_USED = 1L << 32;
112 private static final long META_ALT_USED = 1L << 33;
113 private static final long META_SYM_USED = 1L << 34;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800114
Jeff Brown497a92c2010-09-12 17:55:08 -0700115 private static final long META_CAP_PRESSED = 1L << 40;
116 private static final long META_ALT_PRESSED = 1L << 41;
117 private static final long META_SYM_PRESSED = 1L << 42;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800118
Jeff Brown497a92c2010-09-12 17:55:08 -0700119 private static final long META_CAP_RELEASED = 1L << 48;
120 private static final long META_ALT_RELEASED = 1L << 49;
121 private static final long META_SYM_RELEASED = 1L << 50;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800122
123 private static final long META_SHIFT_MASK = META_SHIFT_ON
124 | META_CAP_LOCKED | META_CAP_USED
125 | META_CAP_PRESSED | META_CAP_RELEASED;
126 private static final long META_ALT_MASK = META_ALT_ON
127 | META_ALT_LOCKED | META_ALT_USED
128 | META_ALT_PRESSED | META_ALT_RELEASED;
129 private static final long META_SYM_MASK = META_SYM_ON
130 | META_SYM_LOCKED | META_SYM_USED
131 | META_SYM_PRESSED | META_SYM_RELEASED;
132
133 private static final Object CAP = new NoCopySpan.Concrete();
134 private static final Object ALT = new NoCopySpan.Concrete();
135 private static final Object SYM = new NoCopySpan.Concrete();
136 private static final Object SELECTING = new NoCopySpan.Concrete();
137
138 /**
139 * Resets all meta state to inactive.
140 */
141 public static void resetMetaState(Spannable text) {
142 text.removeSpan(CAP);
143 text.removeSpan(ALT);
144 text.removeSpan(SYM);
145 text.removeSpan(SELECTING);
146 }
147
148 /**
149 * Gets the state of the meta keys.
150 *
151 * @param text the buffer in which the meta key would have been pressed.
152 *
153 * @return an integer in which each bit set to one represents a pressed
154 * or locked meta key.
155 */
156 public static final int getMetaState(CharSequence text) {
157 return getActive(text, CAP, META_SHIFT_ON, META_CAP_LOCKED) |
158 getActive(text, ALT, META_ALT_ON, META_ALT_LOCKED) |
159 getActive(text, SYM, META_SYM_ON, META_SYM_LOCKED) |
160 getActive(text, SELECTING, META_SELECTING, META_SELECTING);
161 }
162
163 /**
164 * Gets the state of a particular meta key.
165 *
166 * @param meta META_SHIFT_ON, META_ALT_ON, META_SYM_ON, or META_SELECTING
167 * @param text the buffer in which the meta key would have been pressed.
168 *
169 * @return 0 if inactive, 1 if active, 2 if locked.
170 */
171 public static final int getMetaState(CharSequence text, int meta) {
172 switch (meta) {
173 case META_SHIFT_ON:
174 return getActive(text, CAP, 1, 2);
175
176 case META_ALT_ON:
177 return getActive(text, ALT, 1, 2);
178
179 case META_SYM_ON:
180 return getActive(text, SYM, 1, 2);
181
182 case META_SELECTING:
183 return getActive(text, SELECTING, 1, 2);
184
185 default:
186 return 0;
187 }
188 }
189
190 private static int getActive(CharSequence text, Object meta,
191 int on, int lock) {
192 if (!(text instanceof Spanned)) {
193 return 0;
194 }
195
196 Spanned sp = (Spanned) text;
197 int flag = sp.getSpanFlags(meta);
198
199 if (flag == LOCKED) {
200 return lock;
201 } else if (flag != 0) {
202 return on;
203 } else {
204 return 0;
205 }
206 }
207
208 /**
209 * Call this method after you handle a keypress so that the meta
210 * state will be reset to unshifted (if it is not still down)
211 * or primed to be reset to unshifted (once it is released).
212 */
213 public static void adjustMetaAfterKeypress(Spannable content) {
214 adjust(content, CAP);
215 adjust(content, ALT);
216 adjust(content, SYM);
217 }
218
219 /**
220 * Returns true if this object is one that this class would use to
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700221 * keep track of any meta state in the specified text.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800222 */
223 public static boolean isMetaTracker(CharSequence text, Object what) {
224 return what == CAP || what == ALT || what == SYM ||
225 what == SELECTING;
226 }
227
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700228 /**
229 * Returns true if this object is one that this class would use to
230 * keep track of the selecting meta state in the specified text.
231 */
232 public static boolean isSelectingMetaTracker(CharSequence text, Object what) {
233 return what == SELECTING;
234 }
235
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800236 private static void adjust(Spannable content, Object what) {
237 int current = content.getSpanFlags(what);
238
239 if (current == PRESSED)
240 content.setSpan(what, 0, 0, USED);
241 else if (current == RELEASED)
242 content.removeSpan(what);
243 }
244
245 /**
246 * Call this if you are a method that ignores the locked meta state
247 * (arrow keys, for example) and you handle a key.
248 */
249 protected static void resetLockedMeta(Spannable content) {
250 resetLock(content, CAP);
251 resetLock(content, ALT);
252 resetLock(content, SYM);
253 resetLock(content, SELECTING);
254 }
255
256 private static void resetLock(Spannable content, Object what) {
257 int current = content.getSpanFlags(what);
258
259 if (current == LOCKED)
260 content.removeSpan(what);
261 }
262
263 /**
264 * Handles presses of the meta keys.
265 */
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800266 public boolean onKeyDown(View view, Editable content, int keyCode, KeyEvent event) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800267 if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
268 press(content, CAP);
269 return true;
270 }
271
272 if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT
273 || keyCode == KeyEvent.KEYCODE_NUM) {
274 press(content, ALT);
275 return true;
276 }
277
278 if (keyCode == KeyEvent.KEYCODE_SYM) {
279 press(content, SYM);
280 return true;
281 }
282
283 return false; // no super to call through to
284 }
285
286 private void press(Editable content, Object what) {
287 int state = content.getSpanFlags(what);
288
289 if (state == PRESSED)
290 ; // repeat before use
291 else if (state == RELEASED)
292 content.setSpan(what, 0, 0, LOCKED);
293 else if (state == USED)
294 ; // repeat after use
295 else if (state == LOCKED)
296 content.removeSpan(what);
297 else
298 content.setSpan(what, 0, 0, PRESSED);
299 }
300
301 /**
302 * Start selecting text.
303 * @hide pending API review
304 */
305 public static void startSelecting(View view, Spannable content) {
306 content.setSpan(SELECTING, 0, 0, PRESSED);
307 }
308
309 /**
310 * Stop selecting text. This does not actually collapse the selection;
311 * call {@link android.text.Selection#setSelection} too.
312 * @hide pending API review
313 */
314 public static void stopSelecting(View view, Spannable content) {
315 content.removeSpan(SELECTING);
316 }
317
318 /**
319 * Handles release of the meta keys.
320 */
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800321 public boolean onKeyUp(View view, Editable content, int keyCode, KeyEvent event) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800322 if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800323 release(content, CAP, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800324 return true;
325 }
326
327 if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT
328 || keyCode == KeyEvent.KEYCODE_NUM) {
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800329 release(content, ALT, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800330 return true;
331 }
332
333 if (keyCode == KeyEvent.KEYCODE_SYM) {
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800334 release(content, SYM, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800335 return true;
336 }
337
338 return false; // no super to call through to
339 }
340
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800341 private void release(Editable content, Object what, KeyEvent event) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800342 int current = content.getSpanFlags(what);
343
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800344 switch (event.getKeyCharacterMap().getModifierBehavior()) {
345 case KeyCharacterMap.MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED:
346 if (current == USED)
347 content.removeSpan(what);
348 else if (current == PRESSED)
349 content.setSpan(what, 0, 0, RELEASED);
350 break;
351
352 default:
353 content.removeSpan(what);
354 break;
355 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800356 }
357
358 public void clearMetaKeyState(View view, Editable content, int states) {
359 clearMetaKeyState(content, states);
360 }
361
362 public static void clearMetaKeyState(Editable content, int states) {
363 if ((states&META_SHIFT_ON) != 0) content.removeSpan(CAP);
364 if ((states&META_ALT_ON) != 0) content.removeSpan(ALT);
365 if ((states&META_SYM_ON) != 0) content.removeSpan(SYM);
366 if ((states&META_SELECTING) != 0) content.removeSpan(SELECTING);
367 }
368
369 /**
370 * Call this if you are a method that ignores the locked meta state
371 * (arrow keys, for example) and you handle a key.
372 */
373 public static long resetLockedMeta(long state) {
Jeff Brown497a92c2010-09-12 17:55:08 -0700374 if ((state & META_CAP_LOCKED) != 0) {
375 state &= ~META_SHIFT_MASK;
376 }
377 if ((state & META_ALT_LOCKED) != 0) {
378 state &= ~META_ALT_MASK;
379 }
380 if ((state & META_SYM_LOCKED) != 0) {
381 state &= ~META_SYM_MASK;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800382 }
383 return state;
384 }
385
386 // ---------------------------------------------------------------------
387 // Version of API that operates on a state bit mask
388 // ---------------------------------------------------------------------
389
390 /**
391 * Gets the state of the meta keys.
392 *
393 * @param state the current meta state bits.
394 *
395 * @return an integer in which each bit set to one represents a pressed
396 * or locked meta key.
397 */
398 public static final int getMetaState(long state) {
Jeff Brown497a92c2010-09-12 17:55:08 -0700399 int result = 0;
400
401 if ((state & META_CAP_LOCKED) != 0) {
402 result |= META_CAP_LOCKED;
403 } else if ((state & META_SHIFT_ON) != 0) {
404 result |= META_SHIFT_ON;
405 }
406
407 if ((state & META_ALT_LOCKED) != 0) {
408 result |= META_ALT_LOCKED;
409 } else if ((state & META_ALT_ON) != 0) {
410 result |= META_ALT_ON;
411 }
412
413 if ((state & META_SYM_LOCKED) != 0) {
414 result |= META_SYM_LOCKED;
415 } else if ((state & META_SYM_ON) != 0) {
416 result |= META_SYM_ON;
417 }
418
419 return result;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800420 }
421
422 /**
423 * Gets the state of a particular meta key.
424 *
425 * @param state the current state bits.
426 * @param meta META_SHIFT_ON, META_ALT_ON, or META_SYM_ON
427 *
428 * @return 0 if inactive, 1 if active, 2 if locked.
429 */
430 public static final int getMetaState(long state, int meta) {
431 switch (meta) {
432 case META_SHIFT_ON:
Jeff Brown497a92c2010-09-12 17:55:08 -0700433 if ((state & META_CAP_LOCKED) != 0) return 2;
434 if ((state & META_SHIFT_ON) != 0) return 1;
435 return 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800436
437 case META_ALT_ON:
Jeff Brown497a92c2010-09-12 17:55:08 -0700438 if ((state & META_ALT_LOCKED) != 0) return 2;
439 if ((state & META_ALT_ON) != 0) return 1;
440 return 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800441
442 case META_SYM_ON:
Jeff Brown497a92c2010-09-12 17:55:08 -0700443 if ((state & META_SYM_LOCKED) != 0) return 2;
444 if ((state & META_SYM_ON) != 0) return 1;
445 return 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800446
447 default:
448 return 0;
449 }
450 }
451
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800452 /**
453 * Call this method after you handle a keypress so that the meta
454 * state will be reset to unshifted (if it is not still down)
455 * or primed to be reset to unshifted (once it is released). Takes
456 * the current state, returns the new state.
457 */
458 public static long adjustMetaAfterKeypress(long state) {
Jeff Brown497a92c2010-09-12 17:55:08 -0700459 if ((state & META_CAP_PRESSED) != 0) {
460 state = (state & ~META_SHIFT_MASK) | META_SHIFT_ON | META_CAP_USED;
461 } else if ((state & META_CAP_RELEASED) != 0) {
462 state &= ~META_SHIFT_MASK;
463 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800464
Jeff Brown497a92c2010-09-12 17:55:08 -0700465 if ((state & META_ALT_PRESSED) != 0) {
466 state = (state & ~META_ALT_MASK) | META_ALT_ON | META_ALT_USED;
467 } else if ((state & META_ALT_RELEASED) != 0) {
468 state &= ~META_ALT_MASK;
469 }
470
471 if ((state & META_SYM_PRESSED) != 0) {
472 state = (state & ~META_SYM_MASK) | META_SYM_ON | META_SYM_USED;
473 } else if ((state & META_SYM_RELEASED) != 0) {
474 state &= ~META_SYM_MASK;
475 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800476 return state;
477 }
478
479 /**
480 * Handles presses of the meta keys.
481 */
482 public static long handleKeyDown(long state, int keyCode, KeyEvent event) {
483 if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
Jeff Brown497a92c2010-09-12 17:55:08 -0700484 return press(state, META_SHIFT_ON, META_SHIFT_MASK,
485 META_CAP_LOCKED, META_CAP_PRESSED, META_CAP_RELEASED, META_CAP_USED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800486 }
487
488 if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT
489 || keyCode == KeyEvent.KEYCODE_NUM) {
Jeff Brown497a92c2010-09-12 17:55:08 -0700490 return press(state, META_ALT_ON, META_ALT_MASK,
491 META_ALT_LOCKED, META_ALT_PRESSED, META_ALT_RELEASED, META_ALT_USED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800492 }
493
494 if (keyCode == KeyEvent.KEYCODE_SYM) {
Jeff Brown497a92c2010-09-12 17:55:08 -0700495 return press(state, META_SYM_ON, META_SYM_MASK,
496 META_SYM_LOCKED, META_SYM_PRESSED, META_SYM_RELEASED, META_SYM_USED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800497 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800498 return state;
499 }
500
Jeff Brown497a92c2010-09-12 17:55:08 -0700501 private static long press(long state, int what, long mask,
502 long locked, long pressed, long released, long used) {
503 if ((state & pressed) != 0) {
504 // repeat before use
505 } else if ((state & released) != 0) {
506 state = (state &~ mask) | what | locked;
507 } else if ((state & used) != 0) {
508 // repeat after use
509 } else if ((state & locked) != 0) {
510 state &= ~mask;
511 } else {
512 state |= what | pressed;
513 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800514 return state;
515 }
516
517 /**
518 * Handles release of the meta keys.
519 */
520 public static long handleKeyUp(long state, int keyCode, KeyEvent event) {
521 if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
Jeff Brown497a92c2010-09-12 17:55:08 -0700522 return release(state, META_SHIFT_ON, META_SHIFT_MASK,
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800523 META_CAP_PRESSED, META_CAP_RELEASED, META_CAP_USED, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800524 }
525
526 if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT
527 || keyCode == KeyEvent.KEYCODE_NUM) {
Jeff Brown497a92c2010-09-12 17:55:08 -0700528 return release(state, META_ALT_ON, META_ALT_MASK,
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800529 META_ALT_PRESSED, META_ALT_RELEASED, META_ALT_USED, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800530 }
531
532 if (keyCode == KeyEvent.KEYCODE_SYM) {
Jeff Brown497a92c2010-09-12 17:55:08 -0700533 return release(state, META_SYM_ON, META_SYM_MASK,
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800534 META_SYM_PRESSED, META_SYM_RELEASED, META_SYM_USED, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800535 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800536 return state;
537 }
538
Jeff Brown497a92c2010-09-12 17:55:08 -0700539 private static long release(long state, int what, long mask,
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800540 long pressed, long released, long used, KeyEvent event) {
541 switch (event.getKeyCharacterMap().getModifierBehavior()) {
542 case KeyCharacterMap.MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED:
543 if ((state & used) != 0) {
544 state &= ~mask;
545 } else if ((state & pressed) != 0) {
546 state |= what | released;
547 }
548 break;
549
550 default:
551 state &= ~mask;
552 break;
Jeff Brown497a92c2010-09-12 17:55:08 -0700553 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800554 return state;
555 }
556
Jeff Brown497a92c2010-09-12 17:55:08 -0700557 /**
558 * Clears the state of the specified meta key if it is locked.
559 * @param state the meta key state
560 * @param which meta keys to clear, may be a combination of {@link #META_SHIFT_ON},
561 * {@link #META_ALT_ON} or {@link #META_SYM_ON}.
562 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800563 public long clearMetaKeyState(long state, int which) {
Jeff Brown52715a72010-12-08 15:32:37 -0800564 if ((which & META_SHIFT_ON) != 0 && (state & META_CAP_LOCKED) != 0) {
Jeff Brown497a92c2010-09-12 17:55:08 -0700565 state &= ~META_SHIFT_MASK;
566 }
Jeff Brown52715a72010-12-08 15:32:37 -0800567 if ((which & META_ALT_ON) != 0 && (state & META_ALT_LOCKED) != 0) {
Jeff Brown497a92c2010-09-12 17:55:08 -0700568 state &= ~META_ALT_MASK;
569 }
Jeff Brown52715a72010-12-08 15:32:37 -0800570 if ((which & META_SYM_ON) != 0 && (state & META_SYM_LOCKED) != 0) {
Jeff Brown497a92c2010-09-12 17:55:08 -0700571 state &= ~META_SYM_MASK;
572 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800573 return state;
574 }
Jeff Brown497a92c2010-09-12 17:55:08 -0700575
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800576 /**
577 * The meta key has been pressed but has not yet been used.
578 */
579 private static final int PRESSED =
580 Spannable.SPAN_MARK_MARK | (1 << Spannable.SPAN_USER_SHIFT);
581
582 /**
583 * The meta key has been pressed and released but has still
584 * not yet been used.
585 */
586 private static final int RELEASED =
587 Spannable.SPAN_MARK_MARK | (2 << Spannable.SPAN_USER_SHIFT);
588
589 /**
590 * The meta key has been pressed and used but has not yet been released.
591 */
592 private static final int USED =
593 Spannable.SPAN_MARK_MARK | (3 << Spannable.SPAN_USER_SHIFT);
594
595 /**
596 * The meta key has been pressed and released without use, and then
597 * pressed again; it may also have been released again.
598 */
599 private static final int LOCKED =
600 Spannable.SPAN_MARK_MARK | (4 << Spannable.SPAN_USER_SHIFT);
601}
602