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