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.KeyEvent;
     24 import android.view.View;
     25 import android.view.KeyCharacterMap;
     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     /**
    139      * Resets all meta state to inactive.
    140      */
    141     public static void resetMetaState(Spannable text) {
    142         text.removeSpan(CAP);
    143         text.removeSpan(ALT);
    144         text.removeSpan(SYM);
    145         text.removeSpan(SELECTING);
    146     }
    147 
    148     /**
    149      * Gets the state of the meta keys.
    150      *
    151      * @param text the buffer in which the meta key would have been pressed.
    152      *
    153      * @return an integer in which each bit set to one represents a pressed
    154      *         or locked meta key.
    155      */
    156     public static final int getMetaState(CharSequence text) {
    157         return getActive(text, CAP, META_SHIFT_ON, META_CAP_LOCKED) |
    158                getActive(text, ALT, META_ALT_ON, META_ALT_LOCKED) |
    159                getActive(text, SYM, META_SYM_ON, META_SYM_LOCKED) |
    160                getActive(text, SELECTING, META_SELECTING, META_SELECTING);
    161     }
    162 
    163     /**
    164      * Gets the state of a particular meta key.
    165      *
    166      * @param meta META_SHIFT_ON, META_ALT_ON, META_SYM_ON, or META_SELECTING
    167      * @param text the buffer in which the meta key would have been pressed.
    168      *
    169      * @return 0 if inactive, 1 if active, 2 if locked.
    170      */
    171     public static final int getMetaState(CharSequence text, int meta) {
    172         switch (meta) {
    173             case META_SHIFT_ON:
    174                 return getActive(text, CAP, 1, 2);
    175 
    176             case META_ALT_ON:
    177                 return getActive(text, ALT, 1, 2);
    178 
    179             case META_SYM_ON:
    180                 return getActive(text, SYM, 1, 2);
    181 
    182             case META_SELECTING:
    183                 return getActive(text, SELECTING, 1, 2);
    184 
    185             default:
    186                 return 0;
    187         }
    188     }
    189 
    190     private static int getActive(CharSequence text, Object meta,
    191                                  int on, int lock) {
    192         if (!(text instanceof Spanned)) {
    193             return 0;
    194         }
    195 
    196         Spanned sp = (Spanned) text;
    197         int flag = sp.getSpanFlags(meta);
    198 
    199         if (flag == LOCKED) {
    200             return lock;
    201         } else if (flag != 0) {
    202             return on;
    203         } else {
    204             return 0;
    205         }
    206     }
    207 
    208     /**
    209      * Call this method after you handle a keypress so that the meta
    210      * state will be reset to unshifted (if it is not still down)
    211      * or primed to be reset to unshifted (once it is released).
    212      */
    213     public static void adjustMetaAfterKeypress(Spannable content) {
    214         adjust(content, CAP);
    215         adjust(content, ALT);
    216         adjust(content, SYM);
    217     }
    218 
    219     /**
    220      * Returns true if this object is one that this class would use to
    221      * keep track of any meta state in the specified text.
    222      */
    223     public static boolean isMetaTracker(CharSequence text, Object what) {
    224         return what == CAP || what == ALT || what == SYM ||
    225                what == SELECTING;
    226     }
    227 
    228     /**
    229      * Returns true if this object is one that this class would use to
    230      * keep track of the selecting meta state in the specified text.
    231      */
    232     public static boolean isSelectingMetaTracker(CharSequence text, Object what) {
    233         return what == SELECTING;
    234     }
    235 
    236     private static void adjust(Spannable content, Object what) {
    237         int current = content.getSpanFlags(what);
    238 
    239         if (current == PRESSED)
    240             content.setSpan(what, 0, 0, USED);
    241         else if (current == RELEASED)
    242             content.removeSpan(what);
    243     }
    244 
    245     /**
    246      * Call this if you are a method that ignores the locked meta state
    247      * (arrow keys, for example) and you handle a key.
    248      */
    249     protected static void resetLockedMeta(Spannable content) {
    250         resetLock(content, CAP);
    251         resetLock(content, ALT);
    252         resetLock(content, SYM);
    253         resetLock(content, SELECTING);
    254     }
    255 
    256     private static void resetLock(Spannable content, Object what) {
    257         int current = content.getSpanFlags(what);
    258 
    259         if (current == LOCKED)
    260             content.removeSpan(what);
    261     }
    262 
    263     /**
    264      * Handles presses of the meta keys.
    265      */
    266     public boolean onKeyDown(View view, Editable content, int keyCode, KeyEvent event) {
    267         if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
    268             press(content, CAP);
    269             return true;
    270         }
    271 
    272         if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT
    273                 || keyCode == KeyEvent.KEYCODE_NUM) {
    274             press(content, ALT);
    275             return true;
    276         }
    277 
    278         if (keyCode == KeyEvent.KEYCODE_SYM) {
    279             press(content, SYM);
    280             return true;
    281         }
    282 
    283         return false; // no super to call through to
    284     }
    285 
    286     private void press(Editable content, Object what) {
    287         int state = content.getSpanFlags(what);
    288 
    289         if (state == PRESSED)
    290             ; // repeat before use
    291         else if (state == RELEASED)
    292             content.setSpan(what, 0, 0, LOCKED);
    293         else if (state == USED)
    294             ; // repeat after use
    295         else if (state == LOCKED)
    296             content.removeSpan(what);
    297         else
    298             content.setSpan(what, 0, 0, PRESSED);
    299     }
    300 
    301     /**
    302      * Start selecting text.
    303      * @hide pending API review
    304      */
    305     public static void startSelecting(View view, Spannable content) {
    306         content.setSpan(SELECTING, 0, 0, PRESSED);
    307     }
    308 
    309     /**
    310      * Stop selecting text.  This does not actually collapse the selection;
    311      * call {@link android.text.Selection#setSelection} too.
    312      * @hide pending API review
    313      */
    314     public static void stopSelecting(View view, Spannable content) {
    315         content.removeSpan(SELECTING);
    316     }
    317 
    318     /**
    319      * Handles release of the meta keys.
    320      */
    321     public boolean onKeyUp(View view, Editable content, int keyCode, KeyEvent event) {
    322         if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
    323             release(content, CAP, event);
    324             return true;
    325         }
    326 
    327         if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT
    328                 || keyCode == KeyEvent.KEYCODE_NUM) {
    329             release(content, ALT, event);
    330             return true;
    331         }
    332 
    333         if (keyCode == KeyEvent.KEYCODE_SYM) {
    334             release(content, SYM, event);
    335             return true;
    336         }
    337 
    338         return false; // no super to call through to
    339     }
    340 
    341     private void release(Editable content, Object what, KeyEvent event) {
    342         int current = content.getSpanFlags(what);
    343 
    344         switch (event.getKeyCharacterMap().getModifierBehavior()) {
    345             case KeyCharacterMap.MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED:
    346                 if (current == USED)
    347                     content.removeSpan(what);
    348                 else if (current == PRESSED)
    349                     content.setSpan(what, 0, 0, RELEASED);
    350                 break;
    351 
    352             default:
    353                 content.removeSpan(what);
    354                 break;
    355         }
    356     }
    357 
    358     public void clearMetaKeyState(View view, Editable content, int states) {
    359         clearMetaKeyState(content, states);
    360     }
    361 
    362     public static void clearMetaKeyState(Editable content, int states) {
    363         if ((states&META_SHIFT_ON) != 0) content.removeSpan(CAP);
    364         if ((states&META_ALT_ON) != 0) content.removeSpan(ALT);
    365         if ((states&META_SYM_ON) != 0) content.removeSpan(SYM);
    366         if ((states&META_SELECTING) != 0) content.removeSpan(SELECTING);
    367     }
    368 
    369     /**
    370      * Call this if you are a method that ignores the locked meta state
    371      * (arrow keys, for example) and you handle a key.
    372      */
    373     public static long resetLockedMeta(long state) {
    374         if ((state & META_CAP_LOCKED) != 0) {
    375             state &= ~META_SHIFT_MASK;
    376         }
    377         if ((state & META_ALT_LOCKED) != 0) {
    378             state &= ~META_ALT_MASK;
    379         }
    380         if ((state & META_SYM_LOCKED) != 0) {
    381             state &= ~META_SYM_MASK;
    382         }
    383         return state;
    384     }
    385 
    386     // ---------------------------------------------------------------------
    387     // Version of API that operates on a state bit mask
    388     // ---------------------------------------------------------------------
    389 
    390     /**
    391      * Gets the state of the meta keys.
    392      *
    393      * @param state the current meta state bits.
    394      *
    395      * @return an integer in which each bit set to one represents a pressed
    396      *         or locked meta key.
    397      */
    398     public static final int getMetaState(long state) {
    399         int result = 0;
    400 
    401         if ((state & META_CAP_LOCKED) != 0) {
    402             result |= META_CAP_LOCKED;
    403         } else if ((state & META_SHIFT_ON) != 0) {
    404             result |= META_SHIFT_ON;
    405         }
    406 
    407         if ((state & META_ALT_LOCKED) != 0) {
    408             result |= META_ALT_LOCKED;
    409         } else if ((state & META_ALT_ON) != 0) {
    410             result |= META_ALT_ON;
    411         }
    412 
    413         if ((state & META_SYM_LOCKED) != 0) {
    414             result |= META_SYM_LOCKED;
    415         } else if ((state & META_SYM_ON) != 0) {
    416             result |= META_SYM_ON;
    417         }
    418 
    419         return result;
    420     }
    421 
    422     /**
    423      * Gets the state of a particular meta key.
    424      *
    425      * @param state the current state bits.
    426      * @param meta META_SHIFT_ON, META_ALT_ON, or META_SYM_ON
    427      *
    428      * @return 0 if inactive, 1 if active, 2 if locked.
    429      */
    430     public static final int getMetaState(long state, int meta) {
    431         switch (meta) {
    432             case META_SHIFT_ON:
    433                 if ((state & META_CAP_LOCKED) != 0) return 2;
    434                 if ((state & META_SHIFT_ON) != 0) return 1;
    435                 return 0;
    436 
    437             case META_ALT_ON:
    438                 if ((state & META_ALT_LOCKED) != 0) return 2;
    439                 if ((state & META_ALT_ON) != 0) return 1;
    440                 return 0;
    441 
    442             case META_SYM_ON:
    443                 if ((state & META_SYM_LOCKED) != 0) return 2;
    444                 if ((state & META_SYM_ON) != 0) return 1;
    445                 return 0;
    446 
    447             default:
    448                 return 0;
    449         }
    450     }
    451 
    452     /**
    453      * Call this method after you handle a keypress so that the meta
    454      * state will be reset to unshifted (if it is not still down)
    455      * or primed to be reset to unshifted (once it is released).  Takes
    456      * the current state, returns the new state.
    457      */
    458     public static long adjustMetaAfterKeypress(long state) {
    459         if ((state & META_CAP_PRESSED) != 0) {
    460             state = (state & ~META_SHIFT_MASK) | META_SHIFT_ON | META_CAP_USED;
    461         } else if ((state & META_CAP_RELEASED) != 0) {
    462             state &= ~META_SHIFT_MASK;
    463         }
    464 
    465         if ((state & META_ALT_PRESSED) != 0) {
    466             state = (state & ~META_ALT_MASK) | META_ALT_ON | META_ALT_USED;
    467         } else if ((state & META_ALT_RELEASED) != 0) {
    468             state &= ~META_ALT_MASK;
    469         }
    470 
    471         if ((state & META_SYM_PRESSED) != 0) {
    472             state = (state & ~META_SYM_MASK) | META_SYM_ON | META_SYM_USED;
    473         } else if ((state & META_SYM_RELEASED) != 0) {
    474             state &= ~META_SYM_MASK;
    475         }
    476         return state;
    477     }
    478 
    479     /**
    480      * Handles presses of the meta keys.
    481      */
    482     public static long handleKeyDown(long state, int keyCode, KeyEvent event) {
    483         if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
    484             return press(state, META_SHIFT_ON, META_SHIFT_MASK,
    485                     META_CAP_LOCKED, META_CAP_PRESSED, META_CAP_RELEASED, META_CAP_USED);
    486         }
    487 
    488         if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT
    489                 || keyCode == KeyEvent.KEYCODE_NUM) {
    490             return press(state, META_ALT_ON, META_ALT_MASK,
    491                     META_ALT_LOCKED, META_ALT_PRESSED, META_ALT_RELEASED, META_ALT_USED);
    492         }
    493 
    494         if (keyCode == KeyEvent.KEYCODE_SYM) {
    495             return press(state, META_SYM_ON, META_SYM_MASK,
    496                     META_SYM_LOCKED, META_SYM_PRESSED, META_SYM_RELEASED, META_SYM_USED);
    497         }
    498         return state;
    499     }
    500 
    501     private static long press(long state, int what, long mask,
    502             long locked, long pressed, long released, long used) {
    503         if ((state & pressed) != 0) {
    504             // repeat before use
    505         } else if ((state & released) != 0) {
    506             state = (state &~ mask) | what | locked;
    507         } else if ((state & used) != 0) {
    508             // repeat after use
    509         } else if ((state & locked) != 0) {
    510             state &= ~mask;
    511         } else {
    512             state |= what | pressed;
    513         }
    514         return state;
    515     }
    516 
    517     /**
    518      * Handles release of the meta keys.
    519      */
    520     public static long handleKeyUp(long state, int keyCode, KeyEvent event) {
    521         if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
    522             return release(state, META_SHIFT_ON, META_SHIFT_MASK,
    523                     META_CAP_PRESSED, META_CAP_RELEASED, META_CAP_USED, event);
    524         }
    525 
    526         if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT
    527                 || keyCode == KeyEvent.KEYCODE_NUM) {
    528             return release(state, META_ALT_ON, META_ALT_MASK,
    529                     META_ALT_PRESSED, META_ALT_RELEASED, META_ALT_USED, event);
    530         }
    531 
    532         if (keyCode == KeyEvent.KEYCODE_SYM) {
    533             return release(state, META_SYM_ON, META_SYM_MASK,
    534                     META_SYM_PRESSED, META_SYM_RELEASED, META_SYM_USED, event);
    535         }
    536         return state;
    537     }
    538 
    539     private static long release(long state, int what, long mask,
    540             long pressed, long released, long used, KeyEvent event) {
    541         switch (event.getKeyCharacterMap().getModifierBehavior()) {
    542             case KeyCharacterMap.MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED:
    543                 if ((state & used) != 0) {
    544                     state &= ~mask;
    545                 } else if ((state & pressed) != 0) {
    546                     state |= what | released;
    547                 }
    548                 break;
    549 
    550             default:
    551                 state &= ~mask;
    552                 break;
    553         }
    554         return state;
    555     }
    556 
    557     /**
    558      * Clears the state of the specified meta key if it is locked.
    559      * @param state the meta key state
    560      * @param which meta keys to clear, may be a combination of {@link #META_SHIFT_ON},
    561      * {@link #META_ALT_ON} or {@link #META_SYM_ON}.
    562      */
    563     public long clearMetaKeyState(long state, int which) {
    564         if ((which & META_SHIFT_ON) != 0 && (state & META_CAP_LOCKED) != 0) {
    565             state &= ~META_SHIFT_MASK;
    566         }
    567         if ((which & META_ALT_ON) != 0 && (state & META_ALT_LOCKED) != 0) {
    568             state &= ~META_ALT_MASK;
    569         }
    570         if ((which & META_SYM_ON) != 0 && (state & META_SYM_LOCKED) != 0) {
    571             state &= ~META_SYM_MASK;
    572         }
    573         return state;
    574     }
    575 
    576     /**
    577      * The meta key has been pressed but has not yet been used.
    578      */
    579     private static final int PRESSED =
    580         Spannable.SPAN_MARK_MARK | (1 << Spannable.SPAN_USER_SHIFT);
    581 
    582     /**
    583      * The meta key has been pressed and released but has still
    584      * not yet been used.
    585      */
    586     private static final int RELEASED =
    587         Spannable.SPAN_MARK_MARK | (2 << Spannable.SPAN_USER_SHIFT);
    588 
    589     /**
    590      * The meta key has been pressed and used but has not yet been released.
    591      */
    592     private static final int USED =
    593         Spannable.SPAN_MARK_MARK | (3 << Spannable.SPAN_USER_SHIFT);
    594 
    595     /**
    596      * The meta key has been pressed and released without use, and then
    597      * pressed again; it may also have been released again.
    598      */
    599     private static final int LOCKED =
    600         Spannable.SPAN_MARK_MARK | (4 << Spannable.SPAN_USER_SHIFT);
    601 }
    602 
    603