1f7e01e866ffa89b4dd7e66c471ed9fc275a637a2Tadashi G. Takaoka/*
2f7e01e866ffa89b4dd7e66c471ed9fc275a637a2Tadashi G. Takaoka * Copyright (C) 2014, The Android Open Source Project
3f7e01e866ffa89b4dd7e66c471ed9fc275a637a2Tadashi G. Takaoka *
4f7e01e866ffa89b4dd7e66c471ed9fc275a637a2Tadashi G. Takaoka * Licensed under the Apache License, Version 2.0 (the "License");
5f7e01e866ffa89b4dd7e66c471ed9fc275a637a2Tadashi G. Takaoka * you may not use this file except in compliance with the License.
6f7e01e866ffa89b4dd7e66c471ed9fc275a637a2Tadashi G. Takaoka * You may obtain a copy of the License at
7f7e01e866ffa89b4dd7e66c471ed9fc275a637a2Tadashi G. Takaoka *
8f7e01e866ffa89b4dd7e66c471ed9fc275a637a2Tadashi G. Takaoka *     http://www.apache.org/licenses/LICENSE-2.0
9f7e01e866ffa89b4dd7e66c471ed9fc275a637a2Tadashi G. Takaoka *
10f7e01e866ffa89b4dd7e66c471ed9fc275a637a2Tadashi G. Takaoka * Unless required by applicable law or agreed to in writing, software
11f7e01e866ffa89b4dd7e66c471ed9fc275a637a2Tadashi G. Takaoka * distributed under the License is distributed on an "AS IS" BASIS,
12f7e01e866ffa89b4dd7e66c471ed9fc275a637a2Tadashi G. Takaoka * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13f7e01e866ffa89b4dd7e66c471ed9fc275a637a2Tadashi G. Takaoka * See the License for the specific language governing permissions and
14f7e01e866ffa89b4dd7e66c471ed9fc275a637a2Tadashi G. Takaoka * limitations under the License.
15f7e01e866ffa89b4dd7e66c471ed9fc275a637a2Tadashi G. Takaoka */
16f7e01e866ffa89b4dd7e66c471ed9fc275a637a2Tadashi G. Takaoka
17f7e01e866ffa89b4dd7e66c471ed9fc275a637a2Tadashi G. Takaokapackage com.android.inputmethod.latin;
18f7e01e866ffa89b4dd7e66c471ed9fc275a637a2Tadashi G. Takaoka
191fdb8f31562eeef78167585a878eb0450310036bDan Zivkovicimport android.content.res.Resources;
20edd94a449e293c07779ac50e98aca3ad92910d92Dan Zivkovicimport android.util.Log;
219a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhovimport android.util.Pair;
22f7e01e866ffa89b4dd7e66c471ed9fc275a637a2Tadashi G. Takaokaimport android.view.KeyEvent;
23f7e01e866ffa89b4dd7e66c471ed9fc275a637a2Tadashi G. Takaoka
24f7e01e866ffa89b4dd7e66c471ed9fc275a637a2Tadashi G. Takaokaimport com.android.inputmethod.keyboard.KeyboardSwitcher;
25f7e01e866ffa89b4dd7e66c471ed9fc275a637a2Tadashi G. Takaokaimport com.android.inputmethod.latin.settings.Settings;
26f7e01e866ffa89b4dd7e66c471ed9fc275a637a2Tadashi G. Takaoka
279a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhovimport java.util.ArrayList;
281fdb8f31562eeef78167585a878eb0450310036bDan Zivkovicimport java.util.HashSet;
299a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhovimport java.util.List;
301fdb8f31562eeef78167585a878eb0450310036bDan Zivkovicimport java.util.Set;
311fdb8f31562eeef78167585a878eb0450310036bDan Zivkovic
321fdb8f31562eeef78167585a878eb0450310036bDan Zivkovicimport javax.annotation.Nonnull;
331fdb8f31562eeef78167585a878eb0450310036bDan Zivkovic
34f7e01e866ffa89b4dd7e66c471ed9fc275a637a2Tadashi G. Takaoka/**
35f7e01e866ffa89b4dd7e66c471ed9fc275a637a2Tadashi G. Takaoka * A class for detecting Emoji-Alt physical key.
36f7e01e866ffa89b4dd7e66c471ed9fc275a637a2Tadashi G. Takaoka */
37f7e01e866ffa89b4dd7e66c471ed9fc275a637a2Tadashi G. Takaokafinal class EmojiAltPhysicalKeyDetector {
38edd94a449e293c07779ac50e98aca3ad92910d92Dan Zivkovic    private static final String TAG = "EmojiAltPhysicalKeyDetector";
399a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov    private static final boolean DEBUG = false;
40edd94a449e293c07779ac50e98aca3ad92910d92Dan Zivkovic
419a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov    private List<EmojiHotKeys> mHotKeysList;
42edd94a449e293c07779ac50e98aca3ad92910d92Dan Zivkovic
439a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov    private static class HotKeySet extends HashSet<Pair<Integer, Integer>> { };
449a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov
459a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov    private abstract class EmojiHotKeys {
469a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov        private final String mName;
479a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov        private final HotKeySet mKeySet;
489a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov
499a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov        boolean mCanFire;
509a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov        int mMetaState;
519a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov
529a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov        public EmojiHotKeys(final String name, HotKeySet keySet) {
539a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            mName = name;
549a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            mKeySet = keySet;
559a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            mCanFire = false;
569a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov        }
579a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov
589a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov        public void onKeyDown(@Nonnull final KeyEvent keyEvent) {
599a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            if (DEBUG) {
609a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                Log.d(TAG, "EmojiHotKeys.onKeyDown() - " + mName + " - considering " + keyEvent);
619a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            }
629a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov
639a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            final Pair<Integer, Integer> key =
649a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                    Pair.create(keyEvent.getKeyCode(), keyEvent.getMetaState());
659a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            if (mKeySet.contains(key)) {
669a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                if (DEBUG) {
679a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                   Log.d(TAG, "EmojiHotKeys.onKeyDown() - " + mName + " - enabling action");
689a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                }
699a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                mCanFire = true;
709a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                mMetaState = keyEvent.getMetaState();
719a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            } else if (mCanFire) {
729a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                if (DEBUG) {
739a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                   Log.d(TAG, "EmojiHotKeys.onKeyDown() - " + mName + " - disabling action");
749a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                }
759a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                mCanFire = false;
769a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            }
779a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov        }
789a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov
799a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov        public void onKeyUp(@Nonnull final KeyEvent keyEvent) {
809a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            if (DEBUG) {
819a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                Log.d(TAG, "EmojiHotKeys.onKeyUp() - " + mName + " - considering " + keyEvent);
829a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            }
839a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov
849a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            final int keyCode = keyEvent.getKeyCode();
859a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            int metaState = keyEvent.getMetaState();
869a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            if (KeyEvent.isModifierKey(keyCode)) {
879a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                 // Try restoring meta stat in case the released key was a modifier.
889a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                 // I am sure one can come up with scenarios to break this, but it
899a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                 // seems to work well in practice.
909a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                 metaState |= mMetaState;
919a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            }
929a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov
939a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            final Pair<Integer, Integer> key = Pair.create(keyCode, metaState);
949a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            if (mKeySet.contains(key)) {
959a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                if (mCanFire) {
969a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                    if (!keyEvent.isCanceled()) {
979a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                        if (DEBUG) {
989a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                            Log.d(TAG, "EmojiHotKeys.onKeyUp() - " + mName + " - firing action");
999a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                        }
1009a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                        action();
1019a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                    } else {
1029a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                        // This key up event was a part of key combinations and
1039a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                        // should be ignored.
1049a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                        if (DEBUG) {
1059a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                            Log.d(TAG, "EmojiHotKeys.onKeyUp() - " + mName + " - canceled, ignoring action");
1069a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                        }
1079a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                    }
1089a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                    mCanFire = false;
1099a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                }
1109a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            }
1119a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov
1129a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            if (mCanFire) {
1139a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                if (DEBUG) {
1149a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                    Log.d(TAG, "EmojiHotKeys.onKeyUp() - " + mName + " - disabling action");
1159a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                }
1169a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                mCanFire = false;
1179a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            }
1189a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov        }
1199a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov
1209a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov        protected abstract void action();
1219a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov    }
122f7e01e866ffa89b4dd7e66c471ed9fc275a637a2Tadashi G. Takaoka
1231fdb8f31562eeef78167585a878eb0450310036bDan Zivkovic    public EmojiAltPhysicalKeyDetector(@Nonnull final Resources resources) {
1249a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov        mHotKeysList = new ArrayList<EmojiHotKeys>();
1259a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov
1269a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov        final HotKeySet emojiSwitchSet = parseHotKeys(
1279a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                resources, R.array.keyboard_switcher_emoji);
1289a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov        final EmojiHotKeys emojiHotKeys = new EmojiHotKeys("emoji", emojiSwitchSet) {
1299a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            @Override
1309a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            protected void action() {
1319a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                final KeyboardSwitcher switcher = KeyboardSwitcher.getInstance();
1329a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                switcher.onToggleKeyboard(KeyboardSwitcher.KeyboardSwitchState.EMOJI);
1339a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            }
1349a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov        };
1359a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov        mHotKeysList.add(emojiHotKeys);
1369a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov
1379a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov        final HotKeySet symbolsSwitchSet = parseHotKeys(
1381fdb8f31562eeef78167585a878eb0450310036bDan Zivkovic                resources, R.array.keyboard_switcher_symbols_shifted);
1399a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov        final EmojiHotKeys symbolsHotKeys = new EmojiHotKeys("symbols", symbolsSwitchSet) {
1409a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            @Override
1419a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            protected void action() {
1429a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                final KeyboardSwitcher switcher = KeyboardSwitcher.getInstance();
1439a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                switcher.onToggleKeyboard(KeyboardSwitcher.KeyboardSwitchState.SYMBOLS_SHIFTED);
1449a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            }
1459a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov        };
1469a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov        mHotKeysList.add(symbolsHotKeys);
147edd94a449e293c07779ac50e98aca3ad92910d92Dan Zivkovic    }
148edd94a449e293c07779ac50e98aca3ad92910d92Dan Zivkovic
1499a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov    public void onKeyDown(@Nonnull final KeyEvent keyEvent) {
1509a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov        if (DEBUG) {
1519a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            Log.d(TAG, "onKeyDown(): " + keyEvent);
1529a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov        }
1539a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov
1549a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov        if (shouldProcessEvent(keyEvent)) {
1559a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            for (EmojiHotKeys hotKeys : mHotKeysList) {
1569a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                hotKeys.onKeyDown(keyEvent);
1571fdb8f31562eeef78167585a878eb0450310036bDan Zivkovic            }
158f7e01e866ffa89b4dd7e66c471ed9fc275a637a2Tadashi G. Takaoka        }
159f7e01e866ffa89b4dd7e66c471ed9fc275a637a2Tadashi G. Takaoka    }
160f7e01e866ffa89b4dd7e66c471ed9fc275a637a2Tadashi G. Takaoka
1611fdb8f31562eeef78167585a878eb0450310036bDan Zivkovic    public void onKeyUp(@Nonnull final KeyEvent keyEvent) {
1629a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov        if (DEBUG) {
1639a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            Log.d(TAG, "onKeyUp(): " + keyEvent);
1641fdb8f31562eeef78167585a878eb0450310036bDan Zivkovic        }
1659a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov
1669a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov        if (shouldProcessEvent(keyEvent)) {
1679a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            for (EmojiHotKeys hotKeys : mHotKeysList) {
1689a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                hotKeys.onKeyUp(keyEvent);
1699a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            }
170edd94a449e293c07779ac50e98aca3ad92910d92Dan Zivkovic        }
171f7e01e866ffa89b4dd7e66c471ed9fc275a637a2Tadashi G. Takaoka    }
172f7e01e866ffa89b4dd7e66c471ed9fc275a637a2Tadashi G. Takaoka
1739a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov    private static boolean shouldProcessEvent(@Nonnull final KeyEvent keyEvent) {
1749a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov        if (!Settings.getInstance().getCurrent().mEnableEmojiAltPhysicalKey) {
1759a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            // The feature is disabled.
1769a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            if (DEBUG) {
1779a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                Log.d(TAG, "shouldProcessEvent(): Disabled");
1789a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            }
1799a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            return false;
1809a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov        }
1819a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov
1829a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov        return true;
183f7e01e866ffa89b4dd7e66c471ed9fc275a637a2Tadashi G. Takaoka    }
184f7e01e866ffa89b4dd7e66c471ed9fc275a637a2Tadashi G. Takaoka
1859a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov    private static HotKeySet parseHotKeys(
1869a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            @Nonnull final Resources resources, final int resourceId) {
1879a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov        final HotKeySet keySet = new HotKeySet();
1889a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov        final String name = resources.getResourceEntryName(resourceId);
1899a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov        final String[] values = resources.getStringArray(resourceId);
1909a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov        for (int i = 0; values != null && i < values.length; i++) {
1919a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            String[] valuePair = values[i].split(",");
1929a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            if (valuePair.length != 2) {
1939a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                Log.w(TAG, "Expected 2 integers in " + name + "[" + i + "] : " + values[i]);
1941fdb8f31562eeef78167585a878eb0450310036bDan Zivkovic            }
1959a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            try {
1969a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                final Integer keyCode = Integer.parseInt(valuePair[0]);
1979a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                final Integer metaState = Integer.parseInt(valuePair[1]);
1989a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                final Pair<Integer, Integer> key = Pair.create(
1999a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                        keyCode, KeyEvent.normalizeMetaState(metaState));
2009a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                keySet.add(key);
2019a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov            } catch (NumberFormatException e) {
2029a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov                Log.w(TAG, "Failed to parse " + name + "[" + i + "] : " + values[i], e);
2031fdb8f31562eeef78167585a878eb0450310036bDan Zivkovic            }
204f7e01e866ffa89b4dd7e66c471ed9fc275a637a2Tadashi G. Takaoka        }
2059a438a325b28c8f8884737683237f829c7cb5a15Dmitry Torokhov        return keySet;
206f7e01e866ffa89b4dd7e66c471ed9fc275a637a2Tadashi G. Takaoka    }
207f7e01e866ffa89b4dd7e66c471ed9fc275a637a2Tadashi G. Takaoka}
208