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
17package android.text.method;
18
19import android.text.Editable;
20import android.text.NoCopySpan;
21import android.text.Spannable;
22import android.text.Spanned;
23import android.view.KeyEvent;
24import android.view.View;
25import 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 */
73public 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