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