1/* 2 * Copyright (C) 2014, 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 com.android.inputmethod.latin; 18 19import android.content.res.Resources; 20import android.util.Log; 21import android.util.Pair; 22import android.view.KeyEvent; 23 24import com.android.inputmethod.keyboard.KeyboardSwitcher; 25import com.android.inputmethod.latin.settings.Settings; 26 27import java.util.ArrayList; 28import java.util.HashSet; 29import java.util.List; 30import java.util.Set; 31 32import javax.annotation.Nonnull; 33 34/** 35 * A class for detecting Emoji-Alt physical key. 36 */ 37final class EmojiAltPhysicalKeyDetector { 38 private static final String TAG = "EmojiAltPhysicalKeyDetector"; 39 private static final boolean DEBUG = false; 40 41 private List<EmojiHotKeys> mHotKeysList; 42 43 private static class HotKeySet extends HashSet<Pair<Integer, Integer>> { }; 44 45 private abstract class EmojiHotKeys { 46 private final String mName; 47 private final HotKeySet mKeySet; 48 49 boolean mCanFire; 50 int mMetaState; 51 52 public EmojiHotKeys(final String name, HotKeySet keySet) { 53 mName = name; 54 mKeySet = keySet; 55 mCanFire = false; 56 } 57 58 public void onKeyDown(@Nonnull final KeyEvent keyEvent) { 59 if (DEBUG) { 60 Log.d(TAG, "EmojiHotKeys.onKeyDown() - " + mName + " - considering " + keyEvent); 61 } 62 63 final Pair<Integer, Integer> key = 64 Pair.create(keyEvent.getKeyCode(), keyEvent.getMetaState()); 65 if (mKeySet.contains(key)) { 66 if (DEBUG) { 67 Log.d(TAG, "EmojiHotKeys.onKeyDown() - " + mName + " - enabling action"); 68 } 69 mCanFire = true; 70 mMetaState = keyEvent.getMetaState(); 71 } else if (mCanFire) { 72 if (DEBUG) { 73 Log.d(TAG, "EmojiHotKeys.onKeyDown() - " + mName + " - disabling action"); 74 } 75 mCanFire = false; 76 } 77 } 78 79 public void onKeyUp(@Nonnull final KeyEvent keyEvent) { 80 if (DEBUG) { 81 Log.d(TAG, "EmojiHotKeys.onKeyUp() - " + mName + " - considering " + keyEvent); 82 } 83 84 final int keyCode = keyEvent.getKeyCode(); 85 int metaState = keyEvent.getMetaState(); 86 if (KeyEvent.isModifierKey(keyCode)) { 87 // Try restoring meta stat in case the released key was a modifier. 88 // I am sure one can come up with scenarios to break this, but it 89 // seems to work well in practice. 90 metaState |= mMetaState; 91 } 92 93 final Pair<Integer, Integer> key = Pair.create(keyCode, metaState); 94 if (mKeySet.contains(key)) { 95 if (mCanFire) { 96 if (!keyEvent.isCanceled()) { 97 if (DEBUG) { 98 Log.d(TAG, "EmojiHotKeys.onKeyUp() - " + mName + " - firing action"); 99 } 100 action(); 101 } else { 102 // This key up event was a part of key combinations and 103 // should be ignored. 104 if (DEBUG) { 105 Log.d(TAG, "EmojiHotKeys.onKeyUp() - " + mName + " - canceled, ignoring action"); 106 } 107 } 108 mCanFire = false; 109 } 110 } 111 112 if (mCanFire) { 113 if (DEBUG) { 114 Log.d(TAG, "EmojiHotKeys.onKeyUp() - " + mName + " - disabling action"); 115 } 116 mCanFire = false; 117 } 118 } 119 120 protected abstract void action(); 121 } 122 123 public EmojiAltPhysicalKeyDetector(@Nonnull final Resources resources) { 124 mHotKeysList = new ArrayList<EmojiHotKeys>(); 125 126 final HotKeySet emojiSwitchSet = parseHotKeys( 127 resources, R.array.keyboard_switcher_emoji); 128 final EmojiHotKeys emojiHotKeys = new EmojiHotKeys("emoji", emojiSwitchSet) { 129 @Override 130 protected void action() { 131 final KeyboardSwitcher switcher = KeyboardSwitcher.getInstance(); 132 switcher.onToggleKeyboard(KeyboardSwitcher.KeyboardSwitchState.EMOJI); 133 } 134 }; 135 mHotKeysList.add(emojiHotKeys); 136 137 final HotKeySet symbolsSwitchSet = parseHotKeys( 138 resources, R.array.keyboard_switcher_symbols_shifted); 139 final EmojiHotKeys symbolsHotKeys = new EmojiHotKeys("symbols", symbolsSwitchSet) { 140 @Override 141 protected void action() { 142 final KeyboardSwitcher switcher = KeyboardSwitcher.getInstance(); 143 switcher.onToggleKeyboard(KeyboardSwitcher.KeyboardSwitchState.SYMBOLS_SHIFTED); 144 } 145 }; 146 mHotKeysList.add(symbolsHotKeys); 147 } 148 149 public void onKeyDown(@Nonnull final KeyEvent keyEvent) { 150 if (DEBUG) { 151 Log.d(TAG, "onKeyDown(): " + keyEvent); 152 } 153 154 if (shouldProcessEvent(keyEvent)) { 155 for (EmojiHotKeys hotKeys : mHotKeysList) { 156 hotKeys.onKeyDown(keyEvent); 157 } 158 } 159 } 160 161 public void onKeyUp(@Nonnull final KeyEvent keyEvent) { 162 if (DEBUG) { 163 Log.d(TAG, "onKeyUp(): " + keyEvent); 164 } 165 166 if (shouldProcessEvent(keyEvent)) { 167 for (EmojiHotKeys hotKeys : mHotKeysList) { 168 hotKeys.onKeyUp(keyEvent); 169 } 170 } 171 } 172 173 private static boolean shouldProcessEvent(@Nonnull final KeyEvent keyEvent) { 174 if (!Settings.getInstance().getCurrent().mEnableEmojiAltPhysicalKey) { 175 // The feature is disabled. 176 if (DEBUG) { 177 Log.d(TAG, "shouldProcessEvent(): Disabled"); 178 } 179 return false; 180 } 181 182 return true; 183 } 184 185 private static HotKeySet parseHotKeys( 186 @Nonnull final Resources resources, final int resourceId) { 187 final HotKeySet keySet = new HotKeySet(); 188 final String name = resources.getResourceEntryName(resourceId); 189 final String[] values = resources.getStringArray(resourceId); 190 for (int i = 0; values != null && i < values.length; i++) { 191 String[] valuePair = values[i].split(","); 192 if (valuePair.length != 2) { 193 Log.w(TAG, "Expected 2 integers in " + name + "[" + i + "] : " + values[i]); 194 } 195 try { 196 final Integer keyCode = Integer.parseInt(valuePair[0]); 197 final Integer metaState = Integer.parseInt(valuePair[1]); 198 final Pair<Integer, Integer> key = Pair.create( 199 keyCode, KeyEvent.normalizeMetaState(metaState)); 200 keySet.add(key); 201 } catch (NumberFormatException e) { 202 Log.w(TAG, "Failed to parse " + name + "[" + i + "] : " + values[i], e); 203 } 204 } 205 return keySet; 206 } 207} 208