• 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.annotation.UnsupportedAppUsage;
20 import android.text.Editable;
21 import android.text.NoCopySpan;
22 import android.text.Spannable;
23 import android.text.Spanned;
24 import android.view.KeyCharacterMap;
25 import android.view.KeyEvent;
26 import android.view.View;
27 
28 /**
29  * This base class encapsulates the behavior for tracking the state of
30  * meta keys such as SHIFT, ALT and SYM as well as the pseudo-meta state of selecting text.
31  * <p>
32  * Key listeners that care about meta state should inherit from this class;
33  * you should not instantiate this class directly in a client.
34  * </p><p>
35  * This class provides two mechanisms for tracking meta state that can be used
36  * together or independently.
37  * </p>
38  * <ul>
39  * <li>Methods such as {@link #handleKeyDown(long, int, KeyEvent)} and
40  * {@link #getMetaState(long)} operate on a meta key state bit mask.</li>
41  * <li>Methods such as {@link #onKeyDown(View, Editable, int, KeyEvent)} and
42  * {@link #getMetaState(CharSequence, int)} operate on meta key state flags stored
43  * as spans in an {@link Editable} text buffer.  The spans only describe the current
44  * meta key state of the text editor; they do not carry any positional information.</li>
45  * </ul>
46  * <p>
47  * The behavior of this class varies according to the keyboard capabilities
48  * described by the {@link KeyCharacterMap} of the keyboard device such as
49  * the {@link KeyCharacterMap#getModifierBehavior() key modifier behavior}.
50  * </p><p>
51  * {@link MetaKeyKeyListener} implements chorded and toggled key modifiers.
52  * When key modifiers are toggled into a latched or locked state, the state
53  * of the modifier is stored in the {@link Editable} text buffer or in a
54  * meta state integer managed by the client.  These latched or locked modifiers
55  * should be considered to be held <b>in addition to</b> those that the
56  * keyboard already reported as being pressed in {@link KeyEvent#getMetaState()}.
57  * In other words, the {@link MetaKeyKeyListener} augments the meta state
58  * provided by the keyboard; it does not replace it.  This distinction is important
59  * to ensure that meta keys not handled by {@link MetaKeyKeyListener} such as
60  * {@link KeyEvent#KEYCODE_CAPS_LOCK} or {@link KeyEvent#KEYCODE_NUM_LOCK} are
61  * taken into consideration.
62  * </p><p>
63  * To ensure correct meta key behavior, the following pattern should be used
64  * when mapping key codes to characters:
65  * </p>
66  * <code>
67  * private char getUnicodeChar(TextKeyListener listener, KeyEvent event, Editable textBuffer) {
68  *     // Use the combined meta states from the event and the key listener.
69  *     int metaState = event.getMetaState() | listener.getMetaState(textBuffer);
70  *     return event.getUnicodeChar(metaState);
71  * }
72  * </code>
73  */
74 public abstract class MetaKeyKeyListener {
75     /**
76      * Flag that indicates that the SHIFT key is on.
77      * Value equals {@link KeyEvent#META_SHIFT_ON}.
78      */
79     public static final int META_SHIFT_ON = KeyEvent.META_SHIFT_ON;
80     /**
81      * Flag that indicates that the ALT key is on.
82      * Value equals {@link KeyEvent#META_ALT_ON}.
83      */
84     public static final int META_ALT_ON = KeyEvent.META_ALT_ON;
85     /**
86      * Flag that indicates that the SYM key is on.
87      * Value equals {@link KeyEvent#META_SYM_ON}.
88      */
89     public static final int META_SYM_ON = KeyEvent.META_SYM_ON;
90 
91     /**
92      * Flag that indicates that the SHIFT key is locked in CAPS mode.
93      */
94     public static final int META_CAP_LOCKED = KeyEvent.META_CAP_LOCKED;
95     /**
96      * Flag that indicates that the ALT key is locked.
97      */
98     public static final int META_ALT_LOCKED = KeyEvent.META_ALT_LOCKED;
99     /**
100      * Flag that indicates that the SYM key is locked.
101      */
102     public static final int META_SYM_LOCKED = KeyEvent.META_SYM_LOCKED;
103 
104     /**
105      * @hide pending API review
106      */
107     public static final int META_SELECTING = KeyEvent.META_SELECTING;
108 
109     // These bits are privately used by the meta key key listener.
110     // They are deliberately assigned values outside of the representable range of an 'int'
111     // so as not to conflict with any meta key states publicly defined by KeyEvent.
112     private static final long META_CAP_USED = 1L << 32;
113     private static final long META_ALT_USED = 1L << 33;
114     private static final long META_SYM_USED = 1L << 34;
115 
116     private static final long META_CAP_PRESSED = 1L << 40;
117     private static final long META_ALT_PRESSED = 1L << 41;
118     private static final long META_SYM_PRESSED = 1L << 42;
119 
120     private static final long META_CAP_RELEASED = 1L << 48;
121     private static final long META_ALT_RELEASED = 1L << 49;
122     private static final long META_SYM_RELEASED = 1L << 50;
123 
124     private static final long META_SHIFT_MASK = META_SHIFT_ON
125             | META_CAP_LOCKED | META_CAP_USED
126             | META_CAP_PRESSED | META_CAP_RELEASED;
127     private static final long META_ALT_MASK = META_ALT_ON
128             | META_ALT_LOCKED | META_ALT_USED
129             | META_ALT_PRESSED | META_ALT_RELEASED;
130     private static final long META_SYM_MASK = META_SYM_ON
131             | META_SYM_LOCKED | META_SYM_USED
132             | META_SYM_PRESSED | META_SYM_RELEASED;
133 
134     private static final Object CAP = new NoCopySpan.Concrete();
135     private static final Object ALT = new NoCopySpan.Concrete();
136     private static final Object SYM = new NoCopySpan.Concrete();
137     private static final Object SELECTING = new NoCopySpan.Concrete();
138 
139     private static final int PRESSED_RETURN_VALUE = 1;
140     private static final int LOCKED_RETURN_VALUE = 2;
141 
142     /**
143      * Resets all meta state to inactive.
144      */
resetMetaState(Spannable text)145     public static void resetMetaState(Spannable text) {
146         text.removeSpan(CAP);
147         text.removeSpan(ALT);
148         text.removeSpan(SYM);
149         text.removeSpan(SELECTING);
150     }
151 
152     /**
153      * Gets the state of the meta keys.
154      *
155      * @param text the buffer in which the meta key would have been pressed.
156      *
157      * @return an integer in which each bit set to one represents a pressed
158      *         or locked meta key.
159      */
getMetaState(CharSequence text)160     public static final int getMetaState(CharSequence text) {
161         return getActive(text, CAP, META_SHIFT_ON, META_CAP_LOCKED) |
162                getActive(text, ALT, META_ALT_ON, META_ALT_LOCKED) |
163                getActive(text, SYM, META_SYM_ON, META_SYM_LOCKED) |
164                getActive(text, SELECTING, META_SELECTING, META_SELECTING);
165     }
166 
167     /**
168      * Gets the state of the meta keys for a specific key event.
169      *
170      * For input devices that use toggled key modifiers, the `toggled' state
171      * is stored into the text buffer. This method retrieves the meta state
172      * for this event, accounting for the stored state. If the event has been
173      * created by a device that does not support toggled key modifiers, like
174      * a virtual device for example, the stored state is ignored.
175      *
176      * @param text the buffer in which the meta key would have been pressed.
177      * @param event the event for which to evaluate the meta state.
178      * @return an integer in which each bit set to one represents a pressed
179      *         or locked meta key.
180      */
getMetaState(final CharSequence text, final KeyEvent event)181     public static final int getMetaState(final CharSequence text, final KeyEvent event) {
182         int metaState = event.getMetaState();
183         if (event.getKeyCharacterMap().getModifierBehavior()
184                 == KeyCharacterMap.MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED) {
185             metaState |= getMetaState(text);
186         }
187         return metaState;
188     }
189 
190     // As META_SELECTING is @hide we should not mention it in public comments, hence the
191     // omission in @param meta
192     /**
193      * Gets the state of a particular meta key.
194      *
195      * @param meta META_SHIFT_ON, META_ALT_ON, META_SYM_ON
196      * @param text the buffer in which the meta key would have been pressed.
197      *
198      * @return 0 if inactive, 1 if active, 2 if locked.
199      */
getMetaState(CharSequence text, int meta)200     public static final int getMetaState(CharSequence text, int meta) {
201         switch (meta) {
202             case META_SHIFT_ON:
203                 return getActive(text, CAP, PRESSED_RETURN_VALUE, LOCKED_RETURN_VALUE);
204 
205             case META_ALT_ON:
206                 return getActive(text, ALT, PRESSED_RETURN_VALUE, LOCKED_RETURN_VALUE);
207 
208             case META_SYM_ON:
209                 return getActive(text, SYM, PRESSED_RETURN_VALUE, LOCKED_RETURN_VALUE);
210 
211             case META_SELECTING:
212                 return getActive(text, SELECTING, PRESSED_RETURN_VALUE, LOCKED_RETURN_VALUE);
213 
214             default:
215                 return 0;
216         }
217     }
218 
219     /**
220      * Gets the state of a particular meta key to use with a particular key event.
221      *
222      * If the key event has been created by a device that does not support toggled
223      * key modifiers, like a virtual keyboard for example, only the meta state in
224      * the key event is considered.
225      *
226      * @param meta META_SHIFT_ON, META_ALT_ON, META_SYM_ON
227      * @param text the buffer in which the meta key would have been pressed.
228      * @param event the event for which to evaluate the meta state.
229      * @return 0 if inactive, 1 if active, 2 if locked.
230      */
getMetaState(final CharSequence text, final int meta, final KeyEvent event)231     public static final int getMetaState(final CharSequence text, final int meta,
232             final KeyEvent event) {
233         int metaState = event.getMetaState();
234         if (event.getKeyCharacterMap().getModifierBehavior()
235                 == KeyCharacterMap.MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED) {
236             metaState |= getMetaState(text);
237         }
238         if (META_SELECTING == meta) {
239             // #getMetaState(long, int) does not support META_SELECTING, but we want the same
240             // behavior as #getMetaState(CharSequence, int) so we need to do it here
241             if ((metaState & META_SELECTING) != 0) {
242                 // META_SELECTING is only ever set to PRESSED and can't be LOCKED, so return 1
243                 return 1;
244             }
245             return 0;
246         }
247         return getMetaState(metaState, meta);
248     }
249 
getActive(CharSequence text, Object meta, int on, int lock)250     private static int getActive(CharSequence text, Object meta,
251                                  int on, int lock) {
252         if (!(text instanceof Spanned)) {
253             return 0;
254         }
255 
256         Spanned sp = (Spanned) text;
257         int flag = sp.getSpanFlags(meta);
258 
259         if (flag == LOCKED) {
260             return lock;
261         } else if (flag != 0) {
262             return on;
263         } else {
264             return 0;
265         }
266     }
267 
268     /**
269      * Call this method after you handle a keypress so that the meta
270      * state will be reset to unshifted (if it is not still down)
271      * or primed to be reset to unshifted (once it is released).
272      */
adjustMetaAfterKeypress(Spannable content)273     public static void adjustMetaAfterKeypress(Spannable content) {
274         adjust(content, CAP);
275         adjust(content, ALT);
276         adjust(content, SYM);
277     }
278 
279     /**
280      * Returns true if this object is one that this class would use to
281      * keep track of any meta state in the specified text.
282      */
isMetaTracker(CharSequence text, Object what)283     public static boolean isMetaTracker(CharSequence text, Object what) {
284         return what == CAP || what == ALT || what == SYM ||
285                what == SELECTING;
286     }
287 
288     /**
289      * Returns true if this object is one that this class would use to
290      * keep track of the selecting meta state in the specified text.
291      */
isSelectingMetaTracker(CharSequence text, Object what)292     public static boolean isSelectingMetaTracker(CharSequence text, Object what) {
293         return what == SELECTING;
294     }
295 
adjust(Spannable content, Object what)296     private static void adjust(Spannable content, Object what) {
297         int current = content.getSpanFlags(what);
298 
299         if (current == PRESSED)
300             content.setSpan(what, 0, 0, USED);
301         else if (current == RELEASED)
302             content.removeSpan(what);
303     }
304 
305     /**
306      * Call this if you are a method that ignores the locked meta state
307      * (arrow keys, for example) and you handle a key.
308      */
resetLockedMeta(Spannable content)309     protected static void resetLockedMeta(Spannable content) {
310         resetLock(content, CAP);
311         resetLock(content, ALT);
312         resetLock(content, SYM);
313         resetLock(content, SELECTING);
314     }
315 
resetLock(Spannable content, Object what)316     private static void resetLock(Spannable content, Object what) {
317         int current = content.getSpanFlags(what);
318 
319         if (current == LOCKED)
320             content.removeSpan(what);
321     }
322 
323     /**
324      * Handles presses of the meta keys.
325      */
onKeyDown(View view, Editable content, int keyCode, KeyEvent event)326     public boolean onKeyDown(View view, Editable content, int keyCode, KeyEvent event) {
327         if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
328             press(content, CAP);
329             return true;
330         }
331 
332         if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT
333                 || keyCode == KeyEvent.KEYCODE_NUM) {
334             press(content, ALT);
335             return true;
336         }
337 
338         if (keyCode == KeyEvent.KEYCODE_SYM) {
339             press(content, SYM);
340             return true;
341         }
342 
343         return false; // no super to call through to
344     }
345 
press(Editable content, Object what)346     private void press(Editable content, Object what) {
347         int state = content.getSpanFlags(what);
348 
349         if (state == PRESSED)
350             ; // repeat before use
351         else if (state == RELEASED)
352             content.setSpan(what, 0, 0, LOCKED);
353         else if (state == USED)
354             ; // repeat after use
355         else if (state == LOCKED)
356             content.removeSpan(what);
357         else
358             content.setSpan(what, 0, 0, PRESSED);
359     }
360 
361     /**
362      * Start selecting text.
363      * @hide pending API review
364      */
365     @UnsupportedAppUsage
startSelecting(View view, Spannable content)366     public static void startSelecting(View view, Spannable content) {
367         content.setSpan(SELECTING, 0, 0, PRESSED);
368     }
369 
370     /**
371      * Stop selecting text.  This does not actually collapse the selection;
372      * call {@link android.text.Selection#setSelection} too.
373      * @hide pending API review
374      */
375     @UnsupportedAppUsage
stopSelecting(View view, Spannable content)376     public static void stopSelecting(View view, Spannable content) {
377         content.removeSpan(SELECTING);
378     }
379 
380     /**
381      * Handles release of the meta keys.
382      */
onKeyUp(View view, Editable content, int keyCode, KeyEvent event)383     public boolean onKeyUp(View view, Editable content, int keyCode, KeyEvent event) {
384         if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
385             release(content, CAP, event);
386             return true;
387         }
388 
389         if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT
390                 || keyCode == KeyEvent.KEYCODE_NUM) {
391             release(content, ALT, event);
392             return true;
393         }
394 
395         if (keyCode == KeyEvent.KEYCODE_SYM) {
396             release(content, SYM, event);
397             return true;
398         }
399 
400         return false; // no super to call through to
401     }
402 
release(Editable content, Object what, KeyEvent event)403     private void release(Editable content, Object what, KeyEvent event) {
404         int current = content.getSpanFlags(what);
405 
406         switch (event.getKeyCharacterMap().getModifierBehavior()) {
407             case KeyCharacterMap.MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED:
408                 if (current == USED)
409                     content.removeSpan(what);
410                 else if (current == PRESSED)
411                     content.setSpan(what, 0, 0, RELEASED);
412                 break;
413 
414             default:
415                 content.removeSpan(what);
416                 break;
417         }
418     }
419 
clearMetaKeyState(View view, Editable content, int states)420     public void clearMetaKeyState(View view, Editable content, int states) {
421         clearMetaKeyState(content, states);
422     }
423 
clearMetaKeyState(Editable content, int states)424     public static void clearMetaKeyState(Editable content, int states) {
425         if ((states&META_SHIFT_ON) != 0) content.removeSpan(CAP);
426         if ((states&META_ALT_ON) != 0) content.removeSpan(ALT);
427         if ((states&META_SYM_ON) != 0) content.removeSpan(SYM);
428         if ((states&META_SELECTING) != 0) content.removeSpan(SELECTING);
429     }
430 
431     /**
432      * Call this if you are a method that ignores the locked meta state
433      * (arrow keys, for example) and you handle a key.
434      */
resetLockedMeta(long state)435     public static long resetLockedMeta(long state) {
436         if ((state & META_CAP_LOCKED) != 0) {
437             state &= ~META_SHIFT_MASK;
438         }
439         if ((state & META_ALT_LOCKED) != 0) {
440             state &= ~META_ALT_MASK;
441         }
442         if ((state & META_SYM_LOCKED) != 0) {
443             state &= ~META_SYM_MASK;
444         }
445         return state;
446     }
447 
448     // ---------------------------------------------------------------------
449     // Version of API that operates on a state bit mask
450     // ---------------------------------------------------------------------
451 
452     /**
453      * Gets the state of the meta keys.
454      *
455      * @param state the current meta state bits.
456      *
457      * @return an integer in which each bit set to one represents a pressed
458      *         or locked meta key.
459      */
getMetaState(long state)460     public static final int getMetaState(long state) {
461         int result = 0;
462 
463         if ((state & META_CAP_LOCKED) != 0) {
464             result |= META_CAP_LOCKED;
465         } else if ((state & META_SHIFT_ON) != 0) {
466             result |= META_SHIFT_ON;
467         }
468 
469         if ((state & META_ALT_LOCKED) != 0) {
470             result |= META_ALT_LOCKED;
471         } else if ((state & META_ALT_ON) != 0) {
472             result |= META_ALT_ON;
473         }
474 
475         if ((state & META_SYM_LOCKED) != 0) {
476             result |= META_SYM_LOCKED;
477         } else if ((state & META_SYM_ON) != 0) {
478             result |= META_SYM_ON;
479         }
480 
481         return result;
482     }
483 
484     /**
485      * Gets the state of a particular meta key.
486      *
487      * @param state the current state bits.
488      * @param meta META_SHIFT_ON, META_ALT_ON, or META_SYM_ON
489      *
490      * @return 0 if inactive, 1 if active, 2 if locked.
491      */
getMetaState(long state, int meta)492     public static final int getMetaState(long state, int meta) {
493         switch (meta) {
494             case META_SHIFT_ON:
495                 if ((state & META_CAP_LOCKED) != 0) return LOCKED_RETURN_VALUE;
496                 if ((state & META_SHIFT_ON) != 0) return PRESSED_RETURN_VALUE;
497                 return 0;
498 
499             case META_ALT_ON:
500                 if ((state & META_ALT_LOCKED) != 0) return LOCKED_RETURN_VALUE;
501                 if ((state & META_ALT_ON) != 0) return PRESSED_RETURN_VALUE;
502                 return 0;
503 
504             case META_SYM_ON:
505                 if ((state & META_SYM_LOCKED) != 0) return LOCKED_RETURN_VALUE;
506                 if ((state & META_SYM_ON) != 0) return PRESSED_RETURN_VALUE;
507                 return 0;
508 
509             default:
510                 return 0;
511         }
512     }
513 
514     /**
515      * Call this method after you handle a keypress so that the meta
516      * state will be reset to unshifted (if it is not still down)
517      * or primed to be reset to unshifted (once it is released).  Takes
518      * the current state, returns the new state.
519      */
adjustMetaAfterKeypress(long state)520     public static long adjustMetaAfterKeypress(long state) {
521         if ((state & META_CAP_PRESSED) != 0) {
522             state = (state & ~META_SHIFT_MASK) | META_SHIFT_ON | META_CAP_USED;
523         } else if ((state & META_CAP_RELEASED) != 0) {
524             state &= ~META_SHIFT_MASK;
525         }
526 
527         if ((state & META_ALT_PRESSED) != 0) {
528             state = (state & ~META_ALT_MASK) | META_ALT_ON | META_ALT_USED;
529         } else if ((state & META_ALT_RELEASED) != 0) {
530             state &= ~META_ALT_MASK;
531         }
532 
533         if ((state & META_SYM_PRESSED) != 0) {
534             state = (state & ~META_SYM_MASK) | META_SYM_ON | META_SYM_USED;
535         } else if ((state & META_SYM_RELEASED) != 0) {
536             state &= ~META_SYM_MASK;
537         }
538         return state;
539     }
540 
541     /**
542      * Handles presses of the meta keys.
543      */
handleKeyDown(long state, int keyCode, KeyEvent event)544     public static long handleKeyDown(long state, int keyCode, KeyEvent event) {
545         if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
546             return press(state, META_SHIFT_ON, META_SHIFT_MASK,
547                     META_CAP_LOCKED, META_CAP_PRESSED, META_CAP_RELEASED, META_CAP_USED);
548         }
549 
550         if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT
551                 || keyCode == KeyEvent.KEYCODE_NUM) {
552             return press(state, META_ALT_ON, META_ALT_MASK,
553                     META_ALT_LOCKED, META_ALT_PRESSED, META_ALT_RELEASED, META_ALT_USED);
554         }
555 
556         if (keyCode == KeyEvent.KEYCODE_SYM) {
557             return press(state, META_SYM_ON, META_SYM_MASK,
558                     META_SYM_LOCKED, META_SYM_PRESSED, META_SYM_RELEASED, META_SYM_USED);
559         }
560         return state;
561     }
562 
press(long state, int what, long mask, long locked, long pressed, long released, long used)563     private static long press(long state, int what, long mask,
564             long locked, long pressed, long released, long used) {
565         if ((state & pressed) != 0) {
566             // repeat before use
567         } else if ((state & released) != 0) {
568             state = (state &~ mask) | what | locked;
569         } else if ((state & used) != 0) {
570             // repeat after use
571         } else if ((state & locked) != 0) {
572             state &= ~mask;
573         } else {
574             state |= what | pressed;
575         }
576         return state;
577     }
578 
579     /**
580      * Handles release of the meta keys.
581      */
handleKeyUp(long state, int keyCode, KeyEvent event)582     public static long handleKeyUp(long state, int keyCode, KeyEvent event) {
583         if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
584             return release(state, META_SHIFT_ON, META_SHIFT_MASK,
585                     META_CAP_PRESSED, META_CAP_RELEASED, META_CAP_USED, event);
586         }
587 
588         if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT
589                 || keyCode == KeyEvent.KEYCODE_NUM) {
590             return release(state, META_ALT_ON, META_ALT_MASK,
591                     META_ALT_PRESSED, META_ALT_RELEASED, META_ALT_USED, event);
592         }
593 
594         if (keyCode == KeyEvent.KEYCODE_SYM) {
595             return release(state, META_SYM_ON, META_SYM_MASK,
596                     META_SYM_PRESSED, META_SYM_RELEASED, META_SYM_USED, event);
597         }
598         return state;
599     }
600 
release(long state, int what, long mask, long pressed, long released, long used, KeyEvent event)601     private static long release(long state, int what, long mask,
602             long pressed, long released, long used, KeyEvent event) {
603         switch (event.getKeyCharacterMap().getModifierBehavior()) {
604             case KeyCharacterMap.MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED:
605                 if ((state & used) != 0) {
606                     state &= ~mask;
607                 } else if ((state & pressed) != 0) {
608                     state |= what | released;
609                 }
610                 break;
611 
612             default:
613                 state &= ~mask;
614                 break;
615         }
616         return state;
617     }
618 
619     /**
620      * Clears the state of the specified meta key if it is locked.
621      * @param state the meta key state
622      * @param which meta keys to clear, may be a combination of {@link #META_SHIFT_ON},
623      * {@link #META_ALT_ON} or {@link #META_SYM_ON}.
624      */
clearMetaKeyState(long state, int which)625     public long clearMetaKeyState(long state, int which) {
626         if ((which & META_SHIFT_ON) != 0 && (state & META_CAP_LOCKED) != 0) {
627             state &= ~META_SHIFT_MASK;
628         }
629         if ((which & META_ALT_ON) != 0 && (state & META_ALT_LOCKED) != 0) {
630             state &= ~META_ALT_MASK;
631         }
632         if ((which & META_SYM_ON) != 0 && (state & META_SYM_LOCKED) != 0) {
633             state &= ~META_SYM_MASK;
634         }
635         return state;
636     }
637 
638     /**
639      * The meta key has been pressed but has not yet been used.
640      */
641     private static final int PRESSED =
642         Spannable.SPAN_MARK_MARK | (1 << Spannable.SPAN_USER_SHIFT);
643 
644     /**
645      * The meta key has been pressed and released but has still
646      * not yet been used.
647      */
648     private static final int RELEASED =
649         Spannable.SPAN_MARK_MARK | (2 << Spannable.SPAN_USER_SHIFT);
650 
651     /**
652      * The meta key has been pressed and used but has not yet been released.
653      */
654     private static final int USED =
655         Spannable.SPAN_MARK_MARK | (3 << Spannable.SPAN_USER_SHIFT);
656 
657     /**
658      * The meta key has been pressed and released without use, and then
659      * pressed again; it may also have been released again.
660      */
661     private static final int LOCKED =
662         Spannable.SPAN_MARK_MARK | (4 << Spannable.SPAN_USER_SHIFT);
663 }
664