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 17 package com.android.inputmethod.latin; 18 19 import android.content.res.Resources; 20 import android.util.Log; 21 import android.view.KeyEvent; 22 23 import com.android.inputmethod.keyboard.KeyboardSwitcher; 24 import com.android.inputmethod.latin.settings.Settings; 25 26 import java.util.HashMap; 27 import java.util.HashSet; 28 import java.util.Map; 29 import java.util.Set; 30 31 import javax.annotation.Nonnull; 32 33 /** 34 * A class for detecting Emoji-Alt physical key. 35 */ 36 final class EmojiAltPhysicalKeyDetector { 37 private static final String TAG = "EmojiAltPhysicalKeyDetector"; 38 39 private final Map<Integer, Integer> mEmojiSwitcherMap; 40 private final Map<Integer, Integer> mSymbolsShiftedSwitcherMap; 41 private final Map<Integer, Integer> mCombinedSwitcherMap; 42 43 // Set of keys codes that have been used as modifiers. 44 private Set<Integer> mActiveModifiers; 45 EmojiAltPhysicalKeyDetector(@onnull final Resources resources)46 public EmojiAltPhysicalKeyDetector(@Nonnull final Resources resources) { 47 mEmojiSwitcherMap = parseSwitchDefinition(resources, R.array.keyboard_switcher_emoji); 48 mSymbolsShiftedSwitcherMap = parseSwitchDefinition( 49 resources, R.array.keyboard_switcher_symbols_shifted); 50 mCombinedSwitcherMap = new HashMap<>(); 51 mCombinedSwitcherMap.putAll(mEmojiSwitcherMap); 52 mCombinedSwitcherMap.putAll(mSymbolsShiftedSwitcherMap); 53 mActiveModifiers = new HashSet<>(); 54 } 55 parseSwitchDefinition( @onnull final Resources resources, final int resourceId)56 private static Map<Integer, Integer> parseSwitchDefinition( 57 @Nonnull final Resources resources, 58 final int resourceId) { 59 final Map<Integer, Integer> definition = new HashMap<>(); 60 final String name = resources.getResourceEntryName(resourceId); 61 final String[] values = resources.getStringArray(resourceId); 62 for (int i = 0; values != null && i < values.length; i++) { 63 String[] valuePair = values[i].split(","); 64 if (valuePair.length != 2) { 65 Log.w(TAG, "Expected 2 integers in " + name + "[" + i + "] : " + values[i]); 66 } 67 try { 68 definition.put(Integer.parseInt(valuePair[0]), Integer.parseInt(valuePair[1])); 69 } catch (NumberFormatException e) { 70 Log.w(TAG, "Failed to parse " + name + "[" + i + "] : " + values[i], e); 71 } 72 } 73 return definition; 74 } 75 76 /** 77 * Determine whether an up key event came from a mapped modifier key. 78 * 79 * @param keyEvent an up key event. 80 */ onKeyUp(@onnull final KeyEvent keyEvent)81 public void onKeyUp(@Nonnull final KeyEvent keyEvent) { 82 Log.d(TAG, "onKeyUp() : " + keyEvent); 83 if (!Settings.getInstance().getCurrent().mEnableEmojiAltPhysicalKey) { 84 // The feature is disabled. 85 Log.d(TAG, "onKeyUp() : Disabled"); 86 return; 87 } 88 if (keyEvent.isCanceled()) { 89 // This key up event was a part of key combinations and should be ignored. 90 Log.d(TAG, "onKeyUp() : Canceled"); 91 return; 92 } 93 final Integer mappedModifier = getMappedModifier(keyEvent); 94 if (mappedModifier != null) { 95 // If the key was modified by a mapped key, then ignore the next time 96 // the same modifier key comes up. 97 Log.d(TAG, "onKeyUp() : Using Modifier: " + mappedModifier); 98 mActiveModifiers.add(mappedModifier); 99 return; 100 } 101 final int keyCode = keyEvent.getKeyCode(); 102 if (mActiveModifiers.contains(keyCode)) { 103 // Used as a modifier, not a standalone key press. 104 Log.d(TAG, "onKeyUp() : Used as Modifier: " + keyCode); 105 mActiveModifiers.remove(keyCode); 106 return; 107 } 108 if (!isMappedKeyCode(keyEvent)) { 109 // Nothing special about this key. 110 Log.d(TAG, "onKeyUp() : Not Mapped: " + keyCode); 111 return; 112 } 113 final KeyboardSwitcher switcher = KeyboardSwitcher.getInstance(); 114 if (mEmojiSwitcherMap.keySet().contains(keyCode)) { 115 switcher.onToggleKeyboard(KeyboardSwitcher.KeyboardSwitchState.EMOJI); 116 } else if (mSymbolsShiftedSwitcherMap.keySet().contains(keyCode)) { 117 switcher.onToggleKeyboard(KeyboardSwitcher.KeyboardSwitchState.SYMBOLS_SHIFTED); 118 } else { 119 Log.w(TAG, "Cannot toggle on keyCode: " + keyCode); 120 } 121 } 122 123 /** 124 * @param keyEvent pressed key event 125 * @return true iff the user pressed a mapped modifier key. 126 */ isMappedKeyCode(@onnull final KeyEvent keyEvent)127 private boolean isMappedKeyCode(@Nonnull final KeyEvent keyEvent) { 128 return mCombinedSwitcherMap.get(keyEvent.getKeyCode()) != null; 129 } 130 131 /** 132 * @param keyEvent pressed key event 133 * @return the mapped modifier used with this key opress, if any. 134 */ getMappedModifier(@onnull final KeyEvent keyEvent)135 private Integer getMappedModifier(@Nonnull final KeyEvent keyEvent) { 136 final int keyCode = keyEvent.getKeyCode(); 137 final int metaState = keyEvent.getMetaState(); 138 for (int mappedKeyCode : mCombinedSwitcherMap.keySet()) { 139 if (keyCode == mappedKeyCode) { 140 Log.d(TAG, "getMappedModifier() : KeyCode = MappedKeyCode = " + mappedKeyCode); 141 continue; 142 } 143 final Integer mappedMeta = mCombinedSwitcherMap.get(mappedKeyCode); 144 if (mappedMeta == null || mappedMeta.intValue() == -1) { 145 continue; 146 } 147 if ((metaState & mappedMeta) != 0) { 148 Log.d(TAG, "getMappedModifier() : MetaState(" + metaState 149 + ") contains MappedMeta(" + mappedMeta + ")"); 150 return mappedKeyCode; 151 } 152 } 153 return null; 154 } 155 } 156