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