Home | History | Annotate | Download | only in method
      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      */
    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 
    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      */
    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      */
    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      */
    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 
    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      */
    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      */
    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      */
    291     public static boolean isSelectingMetaTracker(CharSequence text, Object what) {
    292         return what == SELECTING;
    293     }
    294 
    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      */
    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      */
    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 
    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      */
    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 
    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 
    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) {
    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      */
    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      */
    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      */
    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      */
    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 
    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      */
    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 
    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      */
    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