• 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.text.Editable;
20 import android.text.NoCopySpan;
21 import android.text.Spannable;
22 import android.text.Spanned;
23 import android.view.KeyCharacterMap;
24 import android.view.KeyEvent;
25 import android.view.View;
26 
27 /**
28  * 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>
72  */
73 public abstract class MetaKeyKeyListener {
74     /**
75      * Flag that indicates that the SHIFT key is on.
76      * Value equals {@link KeyEvent#META_SHIFT_ON}.
77      */
78     public static final int META_SHIFT_ON = KeyEvent.META_SHIFT_ON;
79     /**
80      * Flag that indicates that the ALT key is on.
81      * Value equals {@link KeyEvent#META_ALT_ON}.
82      */
83     public static final int META_ALT_ON = KeyEvent.META_ALT_ON;
84     /**
85      * Flag that indicates that the SYM key is on.
86      * Value equals {@link KeyEvent#META_SYM_ON}.
87      */
88     public static final int META_SYM_ON = KeyEvent.META_SYM_ON;
89 
90     /**
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;
102 
103     /**
104      * @hide pending API review
105      */
106     public static final int META_SELECTING = KeyEvent.META_SELECTING;
107 
108     // 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;
114 
115     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;
118 
119     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;
122 
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     private static final int PRESSED_RETURN_VALUE = 1;
139     private static final int LOCKED_RETURN_VALUE = 2;
140 
141     /**
142      * Resets all meta state to inactive.
143      */
resetMetaState(Spannable text)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      */
getMetaState(CharSequence text)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 
166     /**
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      */
getMetaState(final CharSequence text, final KeyEvent event)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 
189     // As META_SELECTING is @hide we should not mention it in public comments, hence the
190     // omission in @param meta
191     /**
192      * Gets the state of a particular meta key.
193      *
194      * @param meta META_SHIFT_ON, META_ALT_ON, META_SYM_ON
195      * @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      */
getMetaState(CharSequence text, int meta)199     public static final int getMetaState(CharSequence text, int meta) {
200         switch (meta) {
201             case META_SHIFT_ON:
202                 return getActive(text, CAP, PRESSED_RETURN_VALUE, LOCKED_RETURN_VALUE);
203 
204             case META_ALT_ON:
205                 return getActive(text, ALT, PRESSED_RETURN_VALUE, LOCKED_RETURN_VALUE);
206 
207             case META_SYM_ON:
208                 return getActive(text, SYM, PRESSED_RETURN_VALUE, LOCKED_RETURN_VALUE);
209 
210             case META_SELECTING:
211                 return getActive(text, SELECTING, PRESSED_RETURN_VALUE, LOCKED_RETURN_VALUE);
212 
213             default:
214                 return 0;
215         }
216     }
217 
218     /**
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      */
getMetaState(final CharSequence text, final int meta, final KeyEvent event)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 
getActive(CharSequence text, Object meta, int on, int lock)249     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      */
adjustMetaAfterKeypress(Spannable content)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
280      * keep track of any meta state in the specified text.
281      */
isMetaTracker(CharSequence text, Object what)282     public static boolean isMetaTracker(CharSequence text, Object what) {
283         return what == CAP || what == ALT || what == SYM ||
284                what == SELECTING;
285     }
286 
287     /**
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      */
isSelectingMetaTracker(CharSequence text, Object what)291     public static boolean isSelectingMetaTracker(CharSequence text, Object what) {
292         return what == SELECTING;
293     }
294 
adjust(Spannable content, Object what)295     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      */
resetLockedMeta(Spannable content)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 
resetLock(Spannable content, Object what)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      */
onKeyDown(View view, Editable content, int keyCode, KeyEvent event)325     public boolean onKeyDown(View view, Editable content, int keyCode, KeyEvent event) {
326         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 
press(Editable content, Object what)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      */
startSelecting(View view, Spannable content)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      */
stopSelecting(View view, Spannable content)373     public static void stopSelecting(View view, Spannable content) {
374         content.removeSpan(SELECTING);
375     }
376 
377     /**
378      * Handles release of the meta keys.
379      */
onKeyUp(View view, Editable content, int keyCode, KeyEvent event)380     public boolean onKeyUp(View view, Editable content, int keyCode, KeyEvent event) {
381         if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
382             release(content, CAP, event);
383             return true;
384         }
385 
386         if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT
387                 || keyCode == KeyEvent.KEYCODE_NUM) {
388             release(content, ALT, event);
389             return true;
390         }
391 
392         if (keyCode == KeyEvent.KEYCODE_SYM) {
393             release(content, SYM, event);
394             return true;
395         }
396 
397         return false; // no super to call through to
398     }
399 
release(Editable content, Object what, KeyEvent event)400     private void release(Editable content, Object what, KeyEvent event) {
401         int current = content.getSpanFlags(what);
402 
403         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         }
415     }
416 
clearMetaKeyState(View view, Editable content, int states)417     public void clearMetaKeyState(View view, Editable content, int states) {
418         clearMetaKeyState(content, states);
419     }
420 
clearMetaKeyState(Editable content, int states)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      */
resetLockedMeta(long state)432     public static long resetLockedMeta(long state) {
433         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;
441         }
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      */
getMetaState(long state)457     public static final int getMetaState(long state) {
458         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;
479     }
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      */
getMetaState(long state, int meta)489     public static final int getMetaState(long state, int meta) {
490         switch (meta) {
491             case META_SHIFT_ON:
492                 if ((state & META_CAP_LOCKED) != 0) return LOCKED_RETURN_VALUE;
493                 if ((state & META_SHIFT_ON) != 0) return PRESSED_RETURN_VALUE;
494                 return 0;
495 
496             case META_ALT_ON:
497                 if ((state & META_ALT_LOCKED) != 0) return LOCKED_RETURN_VALUE;
498                 if ((state & META_ALT_ON) != 0) return PRESSED_RETURN_VALUE;
499                 return 0;
500 
501             case META_SYM_ON:
502                 if ((state & META_SYM_LOCKED) != 0) return LOCKED_RETURN_VALUE;
503                 if ((state & META_SYM_ON) != 0) return PRESSED_RETURN_VALUE;
504                 return 0;
505 
506             default:
507                 return 0;
508         }
509     }
510 
511     /**
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      */
adjustMetaAfterKeypress(long state)517     public static long adjustMetaAfterKeypress(long state) {
518         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         }
523 
524         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         }
535         return state;
536     }
537 
538     /**
539      * Handles presses of the meta keys.
540      */
handleKeyDown(long state, int keyCode, KeyEvent event)541     public static long handleKeyDown(long state, int keyCode, KeyEvent event) {
542         if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
543             return press(state, META_SHIFT_ON, META_SHIFT_MASK,
544                     META_CAP_LOCKED, META_CAP_PRESSED, META_CAP_RELEASED, META_CAP_USED);
545         }
546 
547         if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT
548                 || keyCode == KeyEvent.KEYCODE_NUM) {
549             return press(state, META_ALT_ON, META_ALT_MASK,
550                     META_ALT_LOCKED, META_ALT_PRESSED, META_ALT_RELEASED, META_ALT_USED);
551         }
552 
553         if (keyCode == KeyEvent.KEYCODE_SYM) {
554             return press(state, META_SYM_ON, META_SYM_MASK,
555                     META_SYM_LOCKED, META_SYM_PRESSED, META_SYM_RELEASED, META_SYM_USED);
556         }
557         return state;
558     }
559 
press(long state, int what, long mask, long locked, long pressed, long released, long used)560     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         }
573         return state;
574     }
575 
576     /**
577      * Handles release of the meta keys.
578      */
handleKeyUp(long state, int keyCode, KeyEvent event)579     public static long handleKeyUp(long state, int keyCode, KeyEvent event) {
580         if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
581             return release(state, META_SHIFT_ON, META_SHIFT_MASK,
582                     META_CAP_PRESSED, META_CAP_RELEASED, META_CAP_USED, event);
583         }
584 
585         if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT
586                 || keyCode == KeyEvent.KEYCODE_NUM) {
587             return release(state, META_ALT_ON, META_ALT_MASK,
588                     META_ALT_PRESSED, META_ALT_RELEASED, META_ALT_USED, event);
589         }
590 
591         if (keyCode == KeyEvent.KEYCODE_SYM) {
592             return release(state, META_SYM_ON, META_SYM_MASK,
593                     META_SYM_PRESSED, META_SYM_RELEASED, META_SYM_USED, event);
594         }
595         return state;
596     }
597 
release(long state, int what, long mask, long pressed, long released, long used, KeyEvent event)598     private static long release(long state, int what, long mask,
599             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;
612         }
613         return state;
614     }
615 
616     /**
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      */
clearMetaKeyState(long state, int which)622     public long clearMetaKeyState(long state, int which) {
623         if ((which & META_SHIFT_ON) != 0 && (state & META_CAP_LOCKED) != 0) {
624             state &= ~META_SHIFT_MASK;
625         }
626         if ((which & META_ALT_ON) != 0 && (state & META_ALT_LOCKED) != 0) {
627             state &= ~META_ALT_MASK;
628         }
629         if ((which & META_SYM_ON) != 0 && (state & META_SYM_LOCKED) != 0) {
630             state &= ~META_SYM_MASK;
631         }
632         return state;
633     }
634 
635     /**
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 }
661