blob: e9db5fde77ed19b17f6aad2164d7591e66d7741f [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
Jean Chalardf9bd5f62013-03-04 18:43:48 -0800138 private static final int PRESSED_RETURN_VALUE = 1;
139 private static final int LOCKED_RETURN_VALUE = 2;
140
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800141 /**
142 * Resets all meta state to inactive.
143 */
144 public static void resetMetaState(Spannable text) {
145 text.removeSpan(CAP);
146 text.removeSpan(ALT);
147 text.removeSpan(SYM);
148 text.removeSpan(SELECTING);
149 }
150
151 /**
152 * Gets the state of the meta keys.
153 *
154 * @param text the buffer in which the meta key would have been pressed.
155 *
156 * @return an integer in which each bit set to one represents a pressed
157 * or locked meta key.
158 */
159 public static final int getMetaState(CharSequence text) {
160 return getActive(text, CAP, META_SHIFT_ON, META_CAP_LOCKED) |
161 getActive(text, ALT, META_ALT_ON, META_ALT_LOCKED) |
162 getActive(text, SYM, META_SYM_ON, META_SYM_LOCKED) |
163 getActive(text, SELECTING, META_SELECTING, META_SELECTING);
164 }
165
Jean Chalard8a1597b2013-03-04 18:45:12 -0800166 /**
167 * Gets the state of the meta keys for a specific key event.
168 *
169 * For input devices that use toggled key modifiers, the `toggled' state
170 * is stored into the text buffer. This method retrieves the meta state
171 * for this event, accounting for the stored state. If the event has been
172 * created by a device that does not support toggled key modifiers, like
173 * a virtual device for example, the stored state is ignored.
174 *
175 * @param text the buffer in which the meta key would have been pressed.
176 * @param event the event for which to evaluate the meta state.
177 * @return an integer in which each bit set to one represents a pressed
178 * or locked meta key.
179 */
180 public static final int getMetaState(final CharSequence text, final KeyEvent event) {
181 int metaState = event.getMetaState();
182 if (event.getKeyCharacterMap().getModifierBehavior()
183 == KeyCharacterMap.MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED) {
184 metaState |= getMetaState(text);
185 }
186 return metaState;
187 }
188
Jean Chalardf9bd5f62013-03-04 18:43:48 -0800189 // As META_SELECTING is @hide we should not mention it in public comments, hence the
190 // omission in @param meta
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800191 /**
192 * Gets the state of a particular meta key.
193 *
Jean Chalardf9bd5f62013-03-04 18:43:48 -0800194 * @param meta META_SHIFT_ON, META_ALT_ON, META_SYM_ON
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800195 * @param text the buffer in which the meta key would have been pressed.
196 *
197 * @return 0 if inactive, 1 if active, 2 if locked.
198 */
199 public static final int getMetaState(CharSequence text, int meta) {
200 switch (meta) {
201 case META_SHIFT_ON:
Jean Chalardf9bd5f62013-03-04 18:43:48 -0800202 return getActive(text, CAP, PRESSED_RETURN_VALUE, LOCKED_RETURN_VALUE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800203
204 case META_ALT_ON:
Jean Chalardf9bd5f62013-03-04 18:43:48 -0800205 return getActive(text, ALT, PRESSED_RETURN_VALUE, LOCKED_RETURN_VALUE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800206
207 case META_SYM_ON:
Jean Chalardf9bd5f62013-03-04 18:43:48 -0800208 return getActive(text, SYM, PRESSED_RETURN_VALUE, LOCKED_RETURN_VALUE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800209
210 case META_SELECTING:
Jean Chalardf9bd5f62013-03-04 18:43:48 -0800211 return getActive(text, SELECTING, PRESSED_RETURN_VALUE, LOCKED_RETURN_VALUE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800212
213 default:
214 return 0;
215 }
216 }
217
Jean Chalard8a1597b2013-03-04 18:45:12 -0800218 /**
219 * Gets the state of a particular meta key to use with a particular key event.
220 *
221 * If the key event has been created by a device that does not support toggled
222 * key modifiers, like a virtual keyboard for example, only the meta state in
223 * the key event is considered.
224 *
225 * @param meta META_SHIFT_ON, META_ALT_ON, META_SYM_ON
226 * @param text the buffer in which the meta key would have been pressed.
227 * @param event the event for which to evaluate the meta state.
228 * @return 0 if inactive, 1 if active, 2 if locked.
229 */
230 public static final int getMetaState(final CharSequence text, final int meta,
231 final KeyEvent event) {
232 int metaState = event.getMetaState();
233 if (event.getKeyCharacterMap().getModifierBehavior()
234 == KeyCharacterMap.MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED) {
235 metaState |= getMetaState(text);
236 }
237 if (META_SELECTING == meta) {
238 // #getMetaState(long, int) does not support META_SELECTING, but we want the same
239 // behavior as #getMetaState(CharSequence, int) so we need to do it here
240 if ((metaState & META_SELECTING) != 0) {
241 // META_SELECTING is only ever set to PRESSED and can't be LOCKED, so return 1
242 return 1;
243 }
244 return 0;
245 }
246 return getMetaState(metaState, meta);
247 }
248
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800249 private static int getActive(CharSequence text, Object meta,
250 int on, int lock) {
251 if (!(text instanceof Spanned)) {
252 return 0;
253 }
254
255 Spanned sp = (Spanned) text;
256 int flag = sp.getSpanFlags(meta);
257
258 if (flag == LOCKED) {
259 return lock;
260 } else if (flag != 0) {
261 return on;
262 } else {
263 return 0;
264 }
265 }
266
267 /**
268 * Call this method after you handle a keypress so that the meta
269 * state will be reset to unshifted (if it is not still down)
270 * or primed to be reset to unshifted (once it is released).
271 */
272 public static void adjustMetaAfterKeypress(Spannable content) {
273 adjust(content, CAP);
274 adjust(content, ALT);
275 adjust(content, SYM);
276 }
277
278 /**
279 * Returns true if this object is one that this class would use to
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700280 * keep track of any meta state in the specified text.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800281 */
282 public static boolean isMetaTracker(CharSequence text, Object what) {
283 return what == CAP || what == ALT || what == SYM ||
284 what == SELECTING;
285 }
286
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700287 /**
288 * Returns true if this object is one that this class would use to
289 * keep track of the selecting meta state in the specified text.
290 */
291 public static boolean isSelectingMetaTracker(CharSequence text, Object what) {
292 return what == SELECTING;
293 }
294
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800295 private static void adjust(Spannable content, Object what) {
296 int current = content.getSpanFlags(what);
297
298 if (current == PRESSED)
299 content.setSpan(what, 0, 0, USED);
300 else if (current == RELEASED)
301 content.removeSpan(what);
302 }
303
304 /**
305 * Call this if you are a method that ignores the locked meta state
306 * (arrow keys, for example) and you handle a key.
307 */
308 protected static void resetLockedMeta(Spannable content) {
309 resetLock(content, CAP);
310 resetLock(content, ALT);
311 resetLock(content, SYM);
312 resetLock(content, SELECTING);
313 }
314
315 private static void resetLock(Spannable content, Object what) {
316 int current = content.getSpanFlags(what);
317
318 if (current == LOCKED)
319 content.removeSpan(what);
320 }
321
322 /**
323 * Handles presses of the meta keys.
324 */
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800325 public boolean onKeyDown(View view, Editable content, int keyCode, KeyEvent event) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800326 if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
327 press(content, CAP);
328 return true;
329 }
330
331 if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT
332 || keyCode == KeyEvent.KEYCODE_NUM) {
333 press(content, ALT);
334 return true;
335 }
336
337 if (keyCode == KeyEvent.KEYCODE_SYM) {
338 press(content, SYM);
339 return true;
340 }
341
342 return false; // no super to call through to
343 }
344
345 private void press(Editable content, Object what) {
346 int state = content.getSpanFlags(what);
347
348 if (state == PRESSED)
349 ; // repeat before use
350 else if (state == RELEASED)
351 content.setSpan(what, 0, 0, LOCKED);
352 else if (state == USED)
353 ; // repeat after use
354 else if (state == LOCKED)
355 content.removeSpan(what);
356 else
357 content.setSpan(what, 0, 0, PRESSED);
358 }
359
360 /**
361 * Start selecting text.
362 * @hide pending API review
363 */
364 public static void startSelecting(View view, Spannable content) {
365 content.setSpan(SELECTING, 0, 0, PRESSED);
366 }
367
368 /**
369 * Stop selecting text. This does not actually collapse the selection;
370 * call {@link android.text.Selection#setSelection} too.
371 * @hide pending API review
372 */
373 public static void stopSelecting(View view, Spannable content) {
374 content.removeSpan(SELECTING);
375 }
376
377 /**
378 * Handles release of the meta keys.
379 */
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800380 public boolean onKeyUp(View view, Editable content, int keyCode, KeyEvent event) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800381 if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800382 release(content, CAP, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800383 return true;
384 }
385
386 if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT
387 || keyCode == KeyEvent.KEYCODE_NUM) {
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800388 release(content, ALT, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800389 return true;
390 }
391
392 if (keyCode == KeyEvent.KEYCODE_SYM) {
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800393 release(content, SYM, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800394 return true;
395 }
396
397 return false; // no super to call through to
398 }
399
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800400 private void release(Editable content, Object what, KeyEvent event) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800401 int current = content.getSpanFlags(what);
402
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800403 switch (event.getKeyCharacterMap().getModifierBehavior()) {
404 case KeyCharacterMap.MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED:
405 if (current == USED)
406 content.removeSpan(what);
407 else if (current == PRESSED)
408 content.setSpan(what, 0, 0, RELEASED);
409 break;
410
411 default:
412 content.removeSpan(what);
413 break;
414 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800415 }
416
417 public void clearMetaKeyState(View view, Editable content, int states) {
418 clearMetaKeyState(content, states);
419 }
420
421 public static void clearMetaKeyState(Editable content, int states) {
422 if ((states&META_SHIFT_ON) != 0) content.removeSpan(CAP);
423 if ((states&META_ALT_ON) != 0) content.removeSpan(ALT);
424 if ((states&META_SYM_ON) != 0) content.removeSpan(SYM);
425 if ((states&META_SELECTING) != 0) content.removeSpan(SELECTING);
426 }
427
428 /**
429 * Call this if you are a method that ignores the locked meta state
430 * (arrow keys, for example) and you handle a key.
431 */
432 public static long resetLockedMeta(long state) {
Jeff Brown497a92c2010-09-12 17:55:08 -0700433 if ((state & META_CAP_LOCKED) != 0) {
434 state &= ~META_SHIFT_MASK;
435 }
436 if ((state & META_ALT_LOCKED) != 0) {
437 state &= ~META_ALT_MASK;
438 }
439 if ((state & META_SYM_LOCKED) != 0) {
440 state &= ~META_SYM_MASK;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800441 }
442 return state;
443 }
444
445 // ---------------------------------------------------------------------
446 // Version of API that operates on a state bit mask
447 // ---------------------------------------------------------------------
448
449 /**
450 * Gets the state of the meta keys.
451 *
452 * @param state the current meta state bits.
453 *
454 * @return an integer in which each bit set to one represents a pressed
455 * or locked meta key.
456 */
457 public static final int getMetaState(long state) {
Jeff Brown497a92c2010-09-12 17:55:08 -0700458 int result = 0;
459
460 if ((state & META_CAP_LOCKED) != 0) {
461 result |= META_CAP_LOCKED;
462 } else if ((state & META_SHIFT_ON) != 0) {
463 result |= META_SHIFT_ON;
464 }
465
466 if ((state & META_ALT_LOCKED) != 0) {
467 result |= META_ALT_LOCKED;
468 } else if ((state & META_ALT_ON) != 0) {
469 result |= META_ALT_ON;
470 }
471
472 if ((state & META_SYM_LOCKED) != 0) {
473 result |= META_SYM_LOCKED;
474 } else if ((state & META_SYM_ON) != 0) {
475 result |= META_SYM_ON;
476 }
477
478 return result;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800479 }
480
481 /**
482 * Gets the state of a particular meta key.
483 *
484 * @param state the current state bits.
485 * @param meta META_SHIFT_ON, META_ALT_ON, or META_SYM_ON
486 *
487 * @return 0 if inactive, 1 if active, 2 if locked.
488 */
489 public static final int getMetaState(long state, int meta) {
490 switch (meta) {
491 case META_SHIFT_ON:
Jean Chalardf9bd5f62013-03-04 18:43:48 -0800492 if ((state & META_CAP_LOCKED) != 0) return LOCKED_RETURN_VALUE;
493 if ((state & META_SHIFT_ON) != 0) return PRESSED_RETURN_VALUE;
Jeff Brown497a92c2010-09-12 17:55:08 -0700494 return 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800495
496 case META_ALT_ON:
Jean Chalardf9bd5f62013-03-04 18:43:48 -0800497 if ((state & META_ALT_LOCKED) != 0) return LOCKED_RETURN_VALUE;
498 if ((state & META_ALT_ON) != 0) return PRESSED_RETURN_VALUE;
Jeff Brown497a92c2010-09-12 17:55:08 -0700499 return 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800500
501 case META_SYM_ON:
Jean Chalardf9bd5f62013-03-04 18:43:48 -0800502 if ((state & META_SYM_LOCKED) != 0) return LOCKED_RETURN_VALUE;
503 if ((state & META_SYM_ON) != 0) return PRESSED_RETURN_VALUE;
Jeff Brown497a92c2010-09-12 17:55:08 -0700504 return 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800505
506 default:
507 return 0;
508 }
509 }
510
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800511 /**
512 * Call this method after you handle a keypress so that the meta
513 * state will be reset to unshifted (if it is not still down)
514 * or primed to be reset to unshifted (once it is released). Takes
515 * the current state, returns the new state.
516 */
517 public static long adjustMetaAfterKeypress(long state) {
Jeff Brown497a92c2010-09-12 17:55:08 -0700518 if ((state & META_CAP_PRESSED) != 0) {
519 state = (state & ~META_SHIFT_MASK) | META_SHIFT_ON | META_CAP_USED;
520 } else if ((state & META_CAP_RELEASED) != 0) {
521 state &= ~META_SHIFT_MASK;
522 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800523
Jeff Brown497a92c2010-09-12 17:55:08 -0700524 if ((state & META_ALT_PRESSED) != 0) {
525 state = (state & ~META_ALT_MASK) | META_ALT_ON | META_ALT_USED;
526 } else if ((state & META_ALT_RELEASED) != 0) {
527 state &= ~META_ALT_MASK;
528 }
529
530 if ((state & META_SYM_PRESSED) != 0) {
531 state = (state & ~META_SYM_MASK) | META_SYM_ON | META_SYM_USED;
532 } else if ((state & META_SYM_RELEASED) != 0) {
533 state &= ~META_SYM_MASK;
534 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800535 return state;
536 }
537
538 /**
539 * Handles presses of the meta keys.
540 */
541 public static long handleKeyDown(long state, int keyCode, KeyEvent event) {
542 if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
Jeff Brown497a92c2010-09-12 17:55:08 -0700543 return press(state, META_SHIFT_ON, META_SHIFT_MASK,
544 META_CAP_LOCKED, META_CAP_PRESSED, META_CAP_RELEASED, META_CAP_USED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800545 }
546
547 if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT
548 || keyCode == KeyEvent.KEYCODE_NUM) {
Jeff Brown497a92c2010-09-12 17:55:08 -0700549 return press(state, META_ALT_ON, META_ALT_MASK,
550 META_ALT_LOCKED, META_ALT_PRESSED, META_ALT_RELEASED, META_ALT_USED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800551 }
552
553 if (keyCode == KeyEvent.KEYCODE_SYM) {
Jeff Brown497a92c2010-09-12 17:55:08 -0700554 return press(state, META_SYM_ON, META_SYM_MASK,
555 META_SYM_LOCKED, META_SYM_PRESSED, META_SYM_RELEASED, META_SYM_USED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800556 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800557 return state;
558 }
559
Jeff Brown497a92c2010-09-12 17:55:08 -0700560 private static long press(long state, int what, long mask,
561 long locked, long pressed, long released, long used) {
562 if ((state & pressed) != 0) {
563 // repeat before use
564 } else if ((state & released) != 0) {
565 state = (state &~ mask) | what | locked;
566 } else if ((state & used) != 0) {
567 // repeat after use
568 } else if ((state & locked) != 0) {
569 state &= ~mask;
570 } else {
571 state |= what | pressed;
572 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800573 return state;
574 }
575
576 /**
577 * Handles release of the meta keys.
578 */
579 public static long handleKeyUp(long state, int keyCode, KeyEvent event) {
580 if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
Jeff Brown497a92c2010-09-12 17:55:08 -0700581 return release(state, META_SHIFT_ON, META_SHIFT_MASK,
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800582 META_CAP_PRESSED, META_CAP_RELEASED, META_CAP_USED, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800583 }
584
585 if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT
586 || keyCode == KeyEvent.KEYCODE_NUM) {
Jeff Brown497a92c2010-09-12 17:55:08 -0700587 return release(state, META_ALT_ON, META_ALT_MASK,
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800588 META_ALT_PRESSED, META_ALT_RELEASED, META_ALT_USED, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800589 }
590
591 if (keyCode == KeyEvent.KEYCODE_SYM) {
Jeff Brown497a92c2010-09-12 17:55:08 -0700592 return release(state, META_SYM_ON, META_SYM_MASK,
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800593 META_SYM_PRESSED, META_SYM_RELEASED, META_SYM_USED, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800594 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800595 return state;
596 }
597
Jeff Brown497a92c2010-09-12 17:55:08 -0700598 private static long release(long state, int what, long mask,
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800599 long pressed, long released, long used, KeyEvent event) {
600 switch (event.getKeyCharacterMap().getModifierBehavior()) {
601 case KeyCharacterMap.MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED:
602 if ((state & used) != 0) {
603 state &= ~mask;
604 } else if ((state & pressed) != 0) {
605 state |= what | released;
606 }
607 break;
608
609 default:
610 state &= ~mask;
611 break;
Jeff Brown497a92c2010-09-12 17:55:08 -0700612 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800613 return state;
614 }
615
Jeff Brown497a92c2010-09-12 17:55:08 -0700616 /**
617 * Clears the state of the specified meta key if it is locked.
618 * @param state the meta key state
619 * @param which meta keys to clear, may be a combination of {@link #META_SHIFT_ON},
620 * {@link #META_ALT_ON} or {@link #META_SYM_ON}.
621 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800622 public long clearMetaKeyState(long state, int which) {
Jeff Brown52715a72010-12-08 15:32:37 -0800623 if ((which & META_SHIFT_ON) != 0 && (state & META_CAP_LOCKED) != 0) {
Jeff Brown497a92c2010-09-12 17:55:08 -0700624 state &= ~META_SHIFT_MASK;
625 }
Jeff Brown52715a72010-12-08 15:32:37 -0800626 if ((which & META_ALT_ON) != 0 && (state & META_ALT_LOCKED) != 0) {
Jeff Brown497a92c2010-09-12 17:55:08 -0700627 state &= ~META_ALT_MASK;
628 }
Jeff Brown52715a72010-12-08 15:32:37 -0800629 if ((which & META_SYM_ON) != 0 && (state & META_SYM_LOCKED) != 0) {
Jeff Brown497a92c2010-09-12 17:55:08 -0700630 state &= ~META_SYM_MASK;
631 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800632 return state;
633 }
Jeff Brown497a92c2010-09-12 17:55:08 -0700634
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800635 /**
636 * The meta key has been pressed but has not yet been used.
637 */
638 private static final int PRESSED =
639 Spannable.SPAN_MARK_MARK | (1 << Spannable.SPAN_USER_SHIFT);
640
641 /**
642 * The meta key has been pressed and released but has still
643 * not yet been used.
644 */
645 private static final int RELEASED =
646 Spannable.SPAN_MARK_MARK | (2 << Spannable.SPAN_USER_SHIFT);
647
648 /**
649 * The meta key has been pressed and used but has not yet been released.
650 */
651 private static final int USED =
652 Spannable.SPAN_MARK_MARK | (3 << Spannable.SPAN_USER_SHIFT);
653
654 /**
655 * The meta key has been pressed and released without use, and then
656 * pressed again; it may also have been released again.
657 */
658 private static final int LOCKED =
659 Spannable.SPAN_MARK_MARK | (4 << Spannable.SPAN_USER_SHIFT);
660}