Loading java/res/values/config.xml +18 −0 Original line number Diff line number Diff line Loading @@ -91,4 +91,22 @@ <fraction name="config_emoji_keyboard_row_height">33%p</fraction> <fraction name="config_emoji_keyboard_key_letter_size">68%p</fraction> <integer name="config_emoji_keyboard_max_page_key_count">21</integer> <!-- Key codes of hardware keys that can be used to toggle the Emoji layout. Each array defines a comma-separated tuple containing: 1. Key code constant from android.view.KeyEvent 2. Meta mask (if any) from android.view.KeyEvent Used in EmojiAltPhysicalKeyDetector and KeyboardSwitcher. --> <string-array name="keyboard_switcher_emoji" translatable="false"> <item>57,16</item> <!-- KeyEvent.KEYCODE_ALT_LEFT , KeyEvent.META_ALT_LEFT_ON --> </string-array> <!-- Key codes of hardware keys that can be used to toggle the Symbols (Shifted) layout. Each array defines a comma-separated tuple containing: 1. Key code constant from android.view.KeyEvent 2. Meta mask (if any) from android.view.KeyEvent Used in EmojiAltPhysicalKeyDetector and KeyboardSwitcher. --> <string-array name="keyboard_switcher_symbols_shifted" translatable="false"> <item>58,32</item> <!-- KeyEvent.KEYCODE_ALT_RIGHT , KeyEvent.META_ALT_RIGHT_ON --> </string-array> </resources> java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +95 −28 Original line number Diff line number Diff line Loading @@ -44,6 +44,8 @@ import com.android.inputmethod.latin.utils.RecapitalizeStatus; import com.android.inputmethod.latin.utils.ResourceUtils; import com.android.inputmethod.latin.utils.ScriptUtils; import javax.annotation.Nonnull; public final class KeyboardSwitcher implements KeyboardState.SwitchActions { private static final String TAG = KeyboardSwitcher.class.getSimpleName(); Loading Loading @@ -139,15 +141,18 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { } } private void setKeyboard(final Keyboard keyboard) { private void setKeyboard( @Nonnull final int keyboardId, @Nonnull final KeyboardSwitchState toggleState) { // Make {@link MainKeyboardView} visible and hide {@link EmojiPalettesView}. final SettingsValues currentSettingsValues = Settings.getInstance().getCurrent(); setMainKeyboardFrame(currentSettingsValues); setMainKeyboardFrame(currentSettingsValues, toggleState); // TODO: pass this object to setKeyboard instead of getting the current values. final MainKeyboardView keyboardView = mKeyboardView; final Keyboard oldKeyboard = keyboardView.getKeyboard(); keyboardView.setKeyboard(keyboard); mCurrentInputView.setKeyboardTopPadding(keyboard.mTopPadding); final Keyboard newKeyboard = mKeyboardLayoutSet.getKeyboard(keyboardId); keyboardView.setKeyboard(newKeyboard); mCurrentInputView.setKeyboardTopPadding(newKeyboard.mTopPadding); keyboardView.setKeyPreviewPopupEnabled( currentSettingsValues.mKeyPreviewPopupOn, currentSettingsValues.mKeyPreviewPopupDismissDelay); Loading @@ -161,9 +166,9 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { currentSettingsValues.mKeyPreviewDismissDuration); keyboardView.updateShortcutKey(mRichImm.isShortcutImeReady()); final boolean subtypeChanged = (oldKeyboard == null) || !keyboard.mId.mSubtype.equals(oldKeyboard.mId.mSubtype); || !newKeyboard.mId.mSubtype.equals(oldKeyboard.mId.mSubtype); final int languageOnSpacebarFormatType = LanguageOnSpacebarUtils .getLanguageOnSpacebarFormatType(keyboard.mId.mSubtype); .getLanguageOnSpacebarFormatType(newKeyboard.mId.mSubtype); final boolean hasMultipleEnabledIMEsOrSubtypes = mRichImm .hasMultipleEnabledIMEsOrSubtypes(true /* shouldIncludeAuxiliarySubtypes */); keyboardView.startDisplayLanguageOnSpacebar(subtypeChanged, languageOnSpacebarFormatType, Loading Loading @@ -205,7 +210,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { if (DEBUG_ACTION) { Log.d(TAG, "setAlphabetKeyboard"); } setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET)); setKeyboard(KeyboardId.ELEMENT_ALPHABET, KeyboardSwitchState.OTHER); } // Implements {@link KeyboardState.SwitchActions}. Loading @@ -214,7 +219,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { if (DEBUG_ACTION) { Log.d(TAG, "setAlphabetManualShiftedKeyboard"); } setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED)); setKeyboard(KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED, KeyboardSwitchState.OTHER); } // Implements {@link KeyboardState.SwitchActions}. Loading @@ -223,7 +228,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { if (DEBUG_ACTION) { Log.d(TAG, "setAlphabetAutomaticShiftedKeyboard"); } setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED)); setKeyboard(KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED, KeyboardSwitchState.OTHER); } // Implements {@link KeyboardState.SwitchActions}. Loading @@ -232,7 +237,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { if (DEBUG_ACTION) { Log.d(TAG, "setAlphabetShiftLockedKeyboard"); } setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED)); setKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED, KeyboardSwitchState.OTHER); } // Implements {@link KeyboardState.SwitchActions}. Loading @@ -241,7 +246,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { if (DEBUG_ACTION) { Log.d(TAG, "setAlphabetShiftLockShiftedKeyboard"); } setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED)); setKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED, KeyboardSwitchState.OTHER); } // Implements {@link KeyboardState.SwitchActions}. Loading @@ -250,11 +255,29 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { if (DEBUG_ACTION) { Log.d(TAG, "setSymbolsKeyboard"); } setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS)); setKeyboard(KeyboardId.ELEMENT_SYMBOLS, KeyboardSwitchState.OTHER); } // Implements {@link KeyboardState.SwitchActions}. @Override public void setSymbolsShiftedKeyboard() { if (DEBUG_ACTION) { Log.d(TAG, "setSymbolsShiftedKeyboard"); } setKeyboard(KeyboardId.ELEMENT_SYMBOLS_SHIFTED, KeyboardSwitchState.SYMBOLS_SHIFTED); } public boolean isImeSuppressedByHardwareKeyboard( @Nonnull final SettingsValues settingsValues, @Nonnull final KeyboardSwitchState toggleState) { return settingsValues.mHasHardwareKeyboard && toggleState == KeyboardSwitchState.HIDDEN; } private void setMainKeyboardFrame(final SettingsValues settingsValues) { final int visibility = settingsValues.mHasHardwareKeyboard ? View.GONE : View.VISIBLE; private void setMainKeyboardFrame( @Nonnull final SettingsValues settingsValues, @Nonnull final KeyboardSwitchState toggleState) { final int visibility = isImeSuppressedByHardwareKeyboard(settingsValues, toggleState) ? View.GONE : View.VISIBLE; mKeyboardView.setVisibility(visibility); // The visibility of {@link #mKeyboardView} must be aligned with {@link #MainKeyboardFrame}. // @see #getVisibleKeyboardView() and Loading Loading @@ -282,24 +305,55 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { mEmojiPalettesView.setVisibility(View.VISIBLE); } public void onToggleEmojiKeyboard() { final boolean needsToLoadKeyboard = (mKeyboardLayoutSet == null); if (needsToLoadKeyboard || !isShowingEmojiPalettes()) { mLatinIME.startShowingInputView(needsToLoadKeyboard); setEmojiKeyboard(); } else { mLatinIME.stopShowingInputView(); setAlphabetKeyboard(); public enum KeyboardSwitchState { HIDDEN(-1), SYMBOLS_SHIFTED(KeyboardId.ELEMENT_SYMBOLS_SHIFTED), EMOJI(KeyboardId.ELEMENT_EMOJI_RECENTS), OTHER(-1); final int mKeyboardId; KeyboardSwitchState(int keyboardId) { mKeyboardId = keyboardId; } } // Implements {@link KeyboardState.SwitchActions}. @Override public void setSymbolsShiftedKeyboard() { if (DEBUG_ACTION) { Log.d(TAG, "setSymbolsShiftedKeyboard"); public KeyboardSwitchState getKeyboardSwitchState() { boolean hidden = !isShowingEmojiPalettes() && (mKeyboardLayoutSet == null || mKeyboardView == null || !mKeyboardView.isShown()); KeyboardSwitchState state; if (hidden) { return KeyboardSwitchState.HIDDEN; } else if (isShowingEmojiPalettes()) { return KeyboardSwitchState.EMOJI; } else if (isShowingKeyboardId(KeyboardId.ELEMENT_SYMBOLS_SHIFTED)) { return KeyboardSwitchState.SYMBOLS_SHIFTED; } return KeyboardSwitchState.OTHER; } public void onToggleKeyboard(@Nonnull final KeyboardSwitchState toggleState) { KeyboardSwitchState currentState = getKeyboardSwitchState(); Log.w(TAG, "onToggleKeyboard() : Current = " + currentState + " : Toggle = " + toggleState); if (currentState == toggleState) { mLatinIME.stopShowingInputView(); mLatinIME.hideWindow(); setAlphabetKeyboard(); } else { mLatinIME.startShowingInputView(true); if (toggleState == KeyboardSwitchState.EMOJI) { setEmojiKeyboard(); } else { mEmojiPalettesView.stopEmojiPalettes(); mEmojiPalettesView.setVisibility(View.GONE); mMainKeyboardFrame.setVisibility(View.VISIBLE); mKeyboardView.setVisibility(View.VISIBLE); setKeyboard(toggleState.mKeyboardId, toggleState); } } setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS_SHIFTED)); } // Future method for requesting an updating to the shift state. Loading Loading @@ -355,6 +409,19 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { mState.onEvent(event, currentAutoCapsState, currentRecapitalizeState); } public boolean isShowingKeyboardId(@Nonnull int... keyboardIds) { if (mKeyboardView == null || !mKeyboardView.isShown()) { return false; } int activeKeyboardId = mKeyboardView.getKeyboard().mId.mElementId; for (int keyboardId : keyboardIds) { if (activeKeyboardId == keyboardId) { return true; } } return false; } public boolean isShowingEmojiPalettes() { return mEmojiPalettesView != null && mEmojiPalettesView.isShown(); } Loading java/src/com/android/inputmethod/latin/EmojiAltPhysicalKeyDetector.java +95 −43 Original line number Diff line number Diff line Loading @@ -16,88 +16,140 @@ package com.android.inputmethod.latin; import android.content.res.Resources; import android.util.Log; import android.view.KeyEvent; import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.latin.settings.Settings; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.annotation.Nonnull; /** * A class for detecting Emoji-Alt physical key. */ final class EmojiAltPhysicalKeyDetector { private static final String TAG = "EmojiAltPhysicalKeyDetector"; private final RichInputConnection mRichInputConnection; private final Map<Integer, Integer> mEmojiSwitcherMap; private final Map<Integer, Integer> mSymbolsShiftedSwitcherMap; private final Map<Integer, Integer> mCombinedSwitcherMap; // True if the Alt key has been used as a modifier. In this case the Alt key up isn't // recognized as an emoji key. private boolean mAltHasBeenUsedAsAModifier; // Set of keys codes that have been used as modifiers. private Set<Integer> mActiveModifiers; public EmojiAltPhysicalKeyDetector(final RichInputConnection richInputConnection) { mRichInputConnection = richInputConnection; public EmojiAltPhysicalKeyDetector(@Nonnull final Resources resources) { mEmojiSwitcherMap = parseSwitchDefinition(resources, R.array.keyboard_switcher_emoji); mSymbolsShiftedSwitcherMap = parseSwitchDefinition( resources, R.array.keyboard_switcher_symbols_shifted); mCombinedSwitcherMap = new HashMap<>(); mCombinedSwitcherMap.putAll(mEmojiSwitcherMap); mCombinedSwitcherMap.putAll(mSymbolsShiftedSwitcherMap); mActiveModifiers = new HashSet<>(); } /** * Record a down key event. * @param keyEvent a down key event. */ public void onKeyDown(final KeyEvent keyEvent) { if (isAltKey(keyEvent)) { mAltHasBeenUsedAsAModifier = false; private static Map<Integer, Integer> parseSwitchDefinition( @Nonnull final Resources resources, final int resourceId) { final Map<Integer, Integer> definition = new HashMap<>(); final String name = resources.getResourceEntryName(resourceId); final String[] values = resources.getStringArray(resourceId); for (int i = 0; values != null && i < values.length; i++) { String[] valuePair = values[i].split(","); if (valuePair.length != 2) { Log.w(TAG, "Expected 2 integers in " + name + "[" + i + "] : " + values[i]); } try { definition.put(Integer.parseInt(valuePair[0]), Integer.parseInt(valuePair[1])); } catch (NumberFormatException e) { Log.w(TAG, "Failed to parse " + name + "[" + i + "] : " + values[i], e); } if (containsAltModifier(keyEvent)) { mAltHasBeenUsedAsAModifier = true; } return definition; } /** * Determine whether an up key event is a special key up or not. * Determine whether an up key event came from a mapped modifier key. * * @param keyEvent an up key event. */ public void onKeyUp(final KeyEvent keyEvent) { public void onKeyUp(@Nonnull final KeyEvent keyEvent) { Log.d(TAG, "onKeyUp() : " + keyEvent); if (!Settings.getInstance().getCurrent().mEnableEmojiAltPhysicalKey) { // The feature is disabled. Log.d(TAG, "onKeyUp() : Disabled"); return; } if (keyEvent.isCanceled()) { // This key up event was a part of key combinations and should be ignored. Log.d(TAG, "onKeyUp() : Canceled"); return; } if (!isAltKey(keyEvent)) { mAltHasBeenUsedAsAModifier |= containsAltModifier(keyEvent); final Integer mappedModifier = getMappedModifier(keyEvent); if (mappedModifier != null) { // If the key was modified by a mapped key, then ignore the next time // the same modifier key comes up. Log.d(TAG, "onKeyUp() : Using Modifier: " + mappedModifier); mActiveModifiers.add(mappedModifier); return; } if (containsAltModifier(keyEvent)) { mAltHasBeenUsedAsAModifier = true; final int keyCode = keyEvent.getKeyCode(); if (mActiveModifiers.contains(keyCode)) { // Used as a modifier, not a standalone key press. Log.d(TAG, "onKeyUp() : Used as Modifier: " + keyCode); mActiveModifiers.remove(keyCode); return; } if (!Settings.getInstance().getCurrent().mEnableEmojiAltPhysicalKey) { if (!isMappedKeyCode(keyEvent)) { // Nothing special about this key. Log.d(TAG, "onKeyUp() : Not Mapped: " + keyCode); return; } if (mAltHasBeenUsedAsAModifier) { return; final KeyboardSwitcher switcher = KeyboardSwitcher.getInstance(); if (mEmojiSwitcherMap.keySet().contains(keyCode)) { switcher.onToggleKeyboard(KeyboardSwitcher.KeyboardSwitchState.EMOJI); } else if (mSymbolsShiftedSwitcherMap.keySet().contains(keyCode)) { switcher.onToggleKeyboard(KeyboardSwitcher.KeyboardSwitchState.SYMBOLS_SHIFTED); } else { Log.w(TAG, "Cannot toggle on keyCode: " + keyCode); } onEmojiAltKeyDetected(); } private static void onEmojiAltKeyDetected() { KeyboardSwitcher.getInstance().onToggleEmojiKeyboard(); /** * @param keyEvent pressed key event * @return true iff the user pressed a mapped modifier key. */ private boolean isMappedKeyCode(@Nonnull final KeyEvent keyEvent) { return mCombinedSwitcherMap.get(keyEvent.getKeyCode()) != null; } private static boolean isAltKey(final KeyEvent keyEvent) { /** * @param keyEvent pressed key event * @return the mapped modifier used with this key opress, if any. */ private Integer getMappedModifier(@Nonnull final KeyEvent keyEvent) { final int keyCode = keyEvent.getKeyCode(); return keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT; } private static boolean containsAltModifier(final KeyEvent keyEvent) { final int metaState = keyEvent.getMetaState(); // TODO: Support multiple keyboards. Take device id into account. switch (keyEvent.getKeyCode()) { case KeyEvent.KEYCODE_ALT_LEFT: // Return true if Left-Alt is pressed with Right-Alt pressed. return (metaState & KeyEvent.META_ALT_RIGHT_ON) != 0; case KeyEvent.KEYCODE_ALT_RIGHT: // Return true if Right-Alt is pressed with Left-Alt pressed. return (metaState & KeyEvent.META_ALT_LEFT_ON) != 0; default: return (metaState & (KeyEvent.META_ALT_LEFT_ON | KeyEvent.META_ALT_RIGHT_ON)) != 0; for (int mappedKeyCode : mCombinedSwitcherMap.keySet()) { if (keyCode == mappedKeyCode) { Log.d(TAG, "getMappedModifier() : KeyCode = MappedKeyCode = " + mappedKeyCode); continue; } final Integer mappedMeta = mCombinedSwitcherMap.get(mappedKeyCode); if (mappedMeta == null || mappedMeta.intValue() == -1) { continue; } if ((metaState & mappedMeta) != 0) { Log.d(TAG, "getMappedModifier() : MetaState(" + metaState + ") contains MappedMeta(" + mappedMeta + ")"); return mappedKeyCode; } } return null; } } java/src/com/android/inputmethod/latin/LatinIME.java +16 −11 Original line number Diff line number Diff line Loading @@ -142,8 +142,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private RichInputMethodManager mRichImm; @UsedForTesting final KeyboardSwitcher mKeyboardSwitcher; private final SubtypeState mSubtypeState = new SubtypeState(); private final EmojiAltPhysicalKeyDetector mEmojiAltPhysicalKeyDetector = new EmojiAltPhysicalKeyDetector(mInputLogic.mConnection); private EmojiAltPhysicalKeyDetector mEmojiAltPhysicalKeyDetector; private StatsUtilsManager mStatsUtilsManager; // Working variable for {@link #startShowingInputView()} and // {@link #onEvaluateInputViewShown()}. Loading Loading @@ -702,6 +701,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mInputLogic.recycle(); } private boolean isImeSuppressedByHardwareKeyboard() { final KeyboardSwitcher switcher = KeyboardSwitcher.getInstance(); return switcher.isImeSuppressedByHardwareKeyboard( mSettings.getCurrent(), switcher.getKeyboardSwitchState()); } @Override public void onConfigurationChanged(final Configuration conf) { SettingsValues settingsValues = mSettings.getCurrent(); Loading @@ -716,7 +721,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // have a change in hardware keyboard configuration. loadSettings(); settingsValues = mSettings.getCurrent(); if (settingsValues.mHasHardwareKeyboard) { if (isImeSuppressedByHardwareKeyboard()) { // We call cleanupInternalStateForFinishInput() because it's the right thing to do; // however, it seems at the moment the framework is passing us a seemingly valid // but actually non-functional InputConnection object. So if this bug ever gets Loading Loading @@ -874,7 +879,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // can go into the correct mode, so we need to do some housekeeping here. final boolean needToCallLoadKeyboardLater; final Suggest suggest = mInputLogic.mSuggest; if (!currentSettingsValues.mHasHardwareKeyboard) { if (!isImeSuppressedByHardwareKeyboard()) { // The app calling setText() has the effect of clearing the composing // span, so we should reset our state unconditionally, even if restarting is true. // We also tell the input logic about the combining rules for the current subtype, so Loading Loading @@ -1118,8 +1123,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen return; } final int inputHeight = mInputView.getHeight(); final boolean hasHardwareKeyboard = settingsValues.mHasHardwareKeyboard; if (hasHardwareKeyboard && visibleKeyboardView.getVisibility() == View.GONE) { if (isImeSuppressedByHardwareKeyboard() && !visibleKeyboardView.isShown()) { // If there is a hardware keyboard and a visible software keyboard view has been hidden, // no visual element will be shown on the screen. outInsets.contentTopInsets = inputHeight; Loading Loading @@ -1165,7 +1169,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen @Override public boolean onShowInputRequested(final int flags, final boolean configChange) { if (Settings.getInstance().getCurrent().mHasHardwareKeyboard) { if (isImeSuppressedByHardwareKeyboard()) { return true; } return super.onShowInputRequested(flags, configChange); Loading @@ -1182,7 +1186,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen @Override public boolean onEvaluateFullscreenMode() { final SettingsValues settingsValues = mSettings.getCurrent(); if (settingsValues.mHasHardwareKeyboard) { if (isImeSuppressedByHardwareKeyboard()) { // If there is a hardware keyboard, disable full screen mode. return false; } Loading Loading @@ -1646,8 +1650,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Hooks for hardware keyboard @Override public boolean onKeyDown(final int keyCode, final KeyEvent keyEvent) { // TODO: This should be processed in {@link InputLogic}. mEmojiAltPhysicalKeyDetector.onKeyDown(keyEvent); if (!ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED) { return super.onKeyDown(keyCode, keyEvent); } Loading @@ -1668,7 +1670,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen @Override public boolean onKeyUp(final int keyCode, final KeyEvent keyEvent) { // TODO: This should be processed in {@link InputLogic}. if (mEmojiAltPhysicalKeyDetector == null) { mEmojiAltPhysicalKeyDetector = new EmojiAltPhysicalKeyDetector( getApplicationContext().getResources()); } mEmojiAltPhysicalKeyDetector.onKeyUp(keyEvent); if (!ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED) { return super.onKeyUp(keyCode, keyEvent); Loading Loading
java/res/values/config.xml +18 −0 Original line number Diff line number Diff line Loading @@ -91,4 +91,22 @@ <fraction name="config_emoji_keyboard_row_height">33%p</fraction> <fraction name="config_emoji_keyboard_key_letter_size">68%p</fraction> <integer name="config_emoji_keyboard_max_page_key_count">21</integer> <!-- Key codes of hardware keys that can be used to toggle the Emoji layout. Each array defines a comma-separated tuple containing: 1. Key code constant from android.view.KeyEvent 2. Meta mask (if any) from android.view.KeyEvent Used in EmojiAltPhysicalKeyDetector and KeyboardSwitcher. --> <string-array name="keyboard_switcher_emoji" translatable="false"> <item>57,16</item> <!-- KeyEvent.KEYCODE_ALT_LEFT , KeyEvent.META_ALT_LEFT_ON --> </string-array> <!-- Key codes of hardware keys that can be used to toggle the Symbols (Shifted) layout. Each array defines a comma-separated tuple containing: 1. Key code constant from android.view.KeyEvent 2. Meta mask (if any) from android.view.KeyEvent Used in EmojiAltPhysicalKeyDetector and KeyboardSwitcher. --> <string-array name="keyboard_switcher_symbols_shifted" translatable="false"> <item>58,32</item> <!-- KeyEvent.KEYCODE_ALT_RIGHT , KeyEvent.META_ALT_RIGHT_ON --> </string-array> </resources>
java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +95 −28 Original line number Diff line number Diff line Loading @@ -44,6 +44,8 @@ import com.android.inputmethod.latin.utils.RecapitalizeStatus; import com.android.inputmethod.latin.utils.ResourceUtils; import com.android.inputmethod.latin.utils.ScriptUtils; import javax.annotation.Nonnull; public final class KeyboardSwitcher implements KeyboardState.SwitchActions { private static final String TAG = KeyboardSwitcher.class.getSimpleName(); Loading Loading @@ -139,15 +141,18 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { } } private void setKeyboard(final Keyboard keyboard) { private void setKeyboard( @Nonnull final int keyboardId, @Nonnull final KeyboardSwitchState toggleState) { // Make {@link MainKeyboardView} visible and hide {@link EmojiPalettesView}. final SettingsValues currentSettingsValues = Settings.getInstance().getCurrent(); setMainKeyboardFrame(currentSettingsValues); setMainKeyboardFrame(currentSettingsValues, toggleState); // TODO: pass this object to setKeyboard instead of getting the current values. final MainKeyboardView keyboardView = mKeyboardView; final Keyboard oldKeyboard = keyboardView.getKeyboard(); keyboardView.setKeyboard(keyboard); mCurrentInputView.setKeyboardTopPadding(keyboard.mTopPadding); final Keyboard newKeyboard = mKeyboardLayoutSet.getKeyboard(keyboardId); keyboardView.setKeyboard(newKeyboard); mCurrentInputView.setKeyboardTopPadding(newKeyboard.mTopPadding); keyboardView.setKeyPreviewPopupEnabled( currentSettingsValues.mKeyPreviewPopupOn, currentSettingsValues.mKeyPreviewPopupDismissDelay); Loading @@ -161,9 +166,9 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { currentSettingsValues.mKeyPreviewDismissDuration); keyboardView.updateShortcutKey(mRichImm.isShortcutImeReady()); final boolean subtypeChanged = (oldKeyboard == null) || !keyboard.mId.mSubtype.equals(oldKeyboard.mId.mSubtype); || !newKeyboard.mId.mSubtype.equals(oldKeyboard.mId.mSubtype); final int languageOnSpacebarFormatType = LanguageOnSpacebarUtils .getLanguageOnSpacebarFormatType(keyboard.mId.mSubtype); .getLanguageOnSpacebarFormatType(newKeyboard.mId.mSubtype); final boolean hasMultipleEnabledIMEsOrSubtypes = mRichImm .hasMultipleEnabledIMEsOrSubtypes(true /* shouldIncludeAuxiliarySubtypes */); keyboardView.startDisplayLanguageOnSpacebar(subtypeChanged, languageOnSpacebarFormatType, Loading Loading @@ -205,7 +210,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { if (DEBUG_ACTION) { Log.d(TAG, "setAlphabetKeyboard"); } setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET)); setKeyboard(KeyboardId.ELEMENT_ALPHABET, KeyboardSwitchState.OTHER); } // Implements {@link KeyboardState.SwitchActions}. Loading @@ -214,7 +219,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { if (DEBUG_ACTION) { Log.d(TAG, "setAlphabetManualShiftedKeyboard"); } setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED)); setKeyboard(KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED, KeyboardSwitchState.OTHER); } // Implements {@link KeyboardState.SwitchActions}. Loading @@ -223,7 +228,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { if (DEBUG_ACTION) { Log.d(TAG, "setAlphabetAutomaticShiftedKeyboard"); } setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED)); setKeyboard(KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED, KeyboardSwitchState.OTHER); } // Implements {@link KeyboardState.SwitchActions}. Loading @@ -232,7 +237,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { if (DEBUG_ACTION) { Log.d(TAG, "setAlphabetShiftLockedKeyboard"); } setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED)); setKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED, KeyboardSwitchState.OTHER); } // Implements {@link KeyboardState.SwitchActions}. Loading @@ -241,7 +246,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { if (DEBUG_ACTION) { Log.d(TAG, "setAlphabetShiftLockShiftedKeyboard"); } setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED)); setKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED, KeyboardSwitchState.OTHER); } // Implements {@link KeyboardState.SwitchActions}. Loading @@ -250,11 +255,29 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { if (DEBUG_ACTION) { Log.d(TAG, "setSymbolsKeyboard"); } setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS)); setKeyboard(KeyboardId.ELEMENT_SYMBOLS, KeyboardSwitchState.OTHER); } // Implements {@link KeyboardState.SwitchActions}. @Override public void setSymbolsShiftedKeyboard() { if (DEBUG_ACTION) { Log.d(TAG, "setSymbolsShiftedKeyboard"); } setKeyboard(KeyboardId.ELEMENT_SYMBOLS_SHIFTED, KeyboardSwitchState.SYMBOLS_SHIFTED); } public boolean isImeSuppressedByHardwareKeyboard( @Nonnull final SettingsValues settingsValues, @Nonnull final KeyboardSwitchState toggleState) { return settingsValues.mHasHardwareKeyboard && toggleState == KeyboardSwitchState.HIDDEN; } private void setMainKeyboardFrame(final SettingsValues settingsValues) { final int visibility = settingsValues.mHasHardwareKeyboard ? View.GONE : View.VISIBLE; private void setMainKeyboardFrame( @Nonnull final SettingsValues settingsValues, @Nonnull final KeyboardSwitchState toggleState) { final int visibility = isImeSuppressedByHardwareKeyboard(settingsValues, toggleState) ? View.GONE : View.VISIBLE; mKeyboardView.setVisibility(visibility); // The visibility of {@link #mKeyboardView} must be aligned with {@link #MainKeyboardFrame}. // @see #getVisibleKeyboardView() and Loading Loading @@ -282,24 +305,55 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { mEmojiPalettesView.setVisibility(View.VISIBLE); } public void onToggleEmojiKeyboard() { final boolean needsToLoadKeyboard = (mKeyboardLayoutSet == null); if (needsToLoadKeyboard || !isShowingEmojiPalettes()) { mLatinIME.startShowingInputView(needsToLoadKeyboard); setEmojiKeyboard(); } else { mLatinIME.stopShowingInputView(); setAlphabetKeyboard(); public enum KeyboardSwitchState { HIDDEN(-1), SYMBOLS_SHIFTED(KeyboardId.ELEMENT_SYMBOLS_SHIFTED), EMOJI(KeyboardId.ELEMENT_EMOJI_RECENTS), OTHER(-1); final int mKeyboardId; KeyboardSwitchState(int keyboardId) { mKeyboardId = keyboardId; } } // Implements {@link KeyboardState.SwitchActions}. @Override public void setSymbolsShiftedKeyboard() { if (DEBUG_ACTION) { Log.d(TAG, "setSymbolsShiftedKeyboard"); public KeyboardSwitchState getKeyboardSwitchState() { boolean hidden = !isShowingEmojiPalettes() && (mKeyboardLayoutSet == null || mKeyboardView == null || !mKeyboardView.isShown()); KeyboardSwitchState state; if (hidden) { return KeyboardSwitchState.HIDDEN; } else if (isShowingEmojiPalettes()) { return KeyboardSwitchState.EMOJI; } else if (isShowingKeyboardId(KeyboardId.ELEMENT_SYMBOLS_SHIFTED)) { return KeyboardSwitchState.SYMBOLS_SHIFTED; } return KeyboardSwitchState.OTHER; } public void onToggleKeyboard(@Nonnull final KeyboardSwitchState toggleState) { KeyboardSwitchState currentState = getKeyboardSwitchState(); Log.w(TAG, "onToggleKeyboard() : Current = " + currentState + " : Toggle = " + toggleState); if (currentState == toggleState) { mLatinIME.stopShowingInputView(); mLatinIME.hideWindow(); setAlphabetKeyboard(); } else { mLatinIME.startShowingInputView(true); if (toggleState == KeyboardSwitchState.EMOJI) { setEmojiKeyboard(); } else { mEmojiPalettesView.stopEmojiPalettes(); mEmojiPalettesView.setVisibility(View.GONE); mMainKeyboardFrame.setVisibility(View.VISIBLE); mKeyboardView.setVisibility(View.VISIBLE); setKeyboard(toggleState.mKeyboardId, toggleState); } } setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS_SHIFTED)); } // Future method for requesting an updating to the shift state. Loading Loading @@ -355,6 +409,19 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { mState.onEvent(event, currentAutoCapsState, currentRecapitalizeState); } public boolean isShowingKeyboardId(@Nonnull int... keyboardIds) { if (mKeyboardView == null || !mKeyboardView.isShown()) { return false; } int activeKeyboardId = mKeyboardView.getKeyboard().mId.mElementId; for (int keyboardId : keyboardIds) { if (activeKeyboardId == keyboardId) { return true; } } return false; } public boolean isShowingEmojiPalettes() { return mEmojiPalettesView != null && mEmojiPalettesView.isShown(); } Loading
java/src/com/android/inputmethod/latin/EmojiAltPhysicalKeyDetector.java +95 −43 Original line number Diff line number Diff line Loading @@ -16,88 +16,140 @@ package com.android.inputmethod.latin; import android.content.res.Resources; import android.util.Log; import android.view.KeyEvent; import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.latin.settings.Settings; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.annotation.Nonnull; /** * A class for detecting Emoji-Alt physical key. */ final class EmojiAltPhysicalKeyDetector { private static final String TAG = "EmojiAltPhysicalKeyDetector"; private final RichInputConnection mRichInputConnection; private final Map<Integer, Integer> mEmojiSwitcherMap; private final Map<Integer, Integer> mSymbolsShiftedSwitcherMap; private final Map<Integer, Integer> mCombinedSwitcherMap; // True if the Alt key has been used as a modifier. In this case the Alt key up isn't // recognized as an emoji key. private boolean mAltHasBeenUsedAsAModifier; // Set of keys codes that have been used as modifiers. private Set<Integer> mActiveModifiers; public EmojiAltPhysicalKeyDetector(final RichInputConnection richInputConnection) { mRichInputConnection = richInputConnection; public EmojiAltPhysicalKeyDetector(@Nonnull final Resources resources) { mEmojiSwitcherMap = parseSwitchDefinition(resources, R.array.keyboard_switcher_emoji); mSymbolsShiftedSwitcherMap = parseSwitchDefinition( resources, R.array.keyboard_switcher_symbols_shifted); mCombinedSwitcherMap = new HashMap<>(); mCombinedSwitcherMap.putAll(mEmojiSwitcherMap); mCombinedSwitcherMap.putAll(mSymbolsShiftedSwitcherMap); mActiveModifiers = new HashSet<>(); } /** * Record a down key event. * @param keyEvent a down key event. */ public void onKeyDown(final KeyEvent keyEvent) { if (isAltKey(keyEvent)) { mAltHasBeenUsedAsAModifier = false; private static Map<Integer, Integer> parseSwitchDefinition( @Nonnull final Resources resources, final int resourceId) { final Map<Integer, Integer> definition = new HashMap<>(); final String name = resources.getResourceEntryName(resourceId); final String[] values = resources.getStringArray(resourceId); for (int i = 0; values != null && i < values.length; i++) { String[] valuePair = values[i].split(","); if (valuePair.length != 2) { Log.w(TAG, "Expected 2 integers in " + name + "[" + i + "] : " + values[i]); } try { definition.put(Integer.parseInt(valuePair[0]), Integer.parseInt(valuePair[1])); } catch (NumberFormatException e) { Log.w(TAG, "Failed to parse " + name + "[" + i + "] : " + values[i], e); } if (containsAltModifier(keyEvent)) { mAltHasBeenUsedAsAModifier = true; } return definition; } /** * Determine whether an up key event is a special key up or not. * Determine whether an up key event came from a mapped modifier key. * * @param keyEvent an up key event. */ public void onKeyUp(final KeyEvent keyEvent) { public void onKeyUp(@Nonnull final KeyEvent keyEvent) { Log.d(TAG, "onKeyUp() : " + keyEvent); if (!Settings.getInstance().getCurrent().mEnableEmojiAltPhysicalKey) { // The feature is disabled. Log.d(TAG, "onKeyUp() : Disabled"); return; } if (keyEvent.isCanceled()) { // This key up event was a part of key combinations and should be ignored. Log.d(TAG, "onKeyUp() : Canceled"); return; } if (!isAltKey(keyEvent)) { mAltHasBeenUsedAsAModifier |= containsAltModifier(keyEvent); final Integer mappedModifier = getMappedModifier(keyEvent); if (mappedModifier != null) { // If the key was modified by a mapped key, then ignore the next time // the same modifier key comes up. Log.d(TAG, "onKeyUp() : Using Modifier: " + mappedModifier); mActiveModifiers.add(mappedModifier); return; } if (containsAltModifier(keyEvent)) { mAltHasBeenUsedAsAModifier = true; final int keyCode = keyEvent.getKeyCode(); if (mActiveModifiers.contains(keyCode)) { // Used as a modifier, not a standalone key press. Log.d(TAG, "onKeyUp() : Used as Modifier: " + keyCode); mActiveModifiers.remove(keyCode); return; } if (!Settings.getInstance().getCurrent().mEnableEmojiAltPhysicalKey) { if (!isMappedKeyCode(keyEvent)) { // Nothing special about this key. Log.d(TAG, "onKeyUp() : Not Mapped: " + keyCode); return; } if (mAltHasBeenUsedAsAModifier) { return; final KeyboardSwitcher switcher = KeyboardSwitcher.getInstance(); if (mEmojiSwitcherMap.keySet().contains(keyCode)) { switcher.onToggleKeyboard(KeyboardSwitcher.KeyboardSwitchState.EMOJI); } else if (mSymbolsShiftedSwitcherMap.keySet().contains(keyCode)) { switcher.onToggleKeyboard(KeyboardSwitcher.KeyboardSwitchState.SYMBOLS_SHIFTED); } else { Log.w(TAG, "Cannot toggle on keyCode: " + keyCode); } onEmojiAltKeyDetected(); } private static void onEmojiAltKeyDetected() { KeyboardSwitcher.getInstance().onToggleEmojiKeyboard(); /** * @param keyEvent pressed key event * @return true iff the user pressed a mapped modifier key. */ private boolean isMappedKeyCode(@Nonnull final KeyEvent keyEvent) { return mCombinedSwitcherMap.get(keyEvent.getKeyCode()) != null; } private static boolean isAltKey(final KeyEvent keyEvent) { /** * @param keyEvent pressed key event * @return the mapped modifier used with this key opress, if any. */ private Integer getMappedModifier(@Nonnull final KeyEvent keyEvent) { final int keyCode = keyEvent.getKeyCode(); return keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT; } private static boolean containsAltModifier(final KeyEvent keyEvent) { final int metaState = keyEvent.getMetaState(); // TODO: Support multiple keyboards. Take device id into account. switch (keyEvent.getKeyCode()) { case KeyEvent.KEYCODE_ALT_LEFT: // Return true if Left-Alt is pressed with Right-Alt pressed. return (metaState & KeyEvent.META_ALT_RIGHT_ON) != 0; case KeyEvent.KEYCODE_ALT_RIGHT: // Return true if Right-Alt is pressed with Left-Alt pressed. return (metaState & KeyEvent.META_ALT_LEFT_ON) != 0; default: return (metaState & (KeyEvent.META_ALT_LEFT_ON | KeyEvent.META_ALT_RIGHT_ON)) != 0; for (int mappedKeyCode : mCombinedSwitcherMap.keySet()) { if (keyCode == mappedKeyCode) { Log.d(TAG, "getMappedModifier() : KeyCode = MappedKeyCode = " + mappedKeyCode); continue; } final Integer mappedMeta = mCombinedSwitcherMap.get(mappedKeyCode); if (mappedMeta == null || mappedMeta.intValue() == -1) { continue; } if ((metaState & mappedMeta) != 0) { Log.d(TAG, "getMappedModifier() : MetaState(" + metaState + ") contains MappedMeta(" + mappedMeta + ")"); return mappedKeyCode; } } return null; } }
java/src/com/android/inputmethod/latin/LatinIME.java +16 −11 Original line number Diff line number Diff line Loading @@ -142,8 +142,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private RichInputMethodManager mRichImm; @UsedForTesting final KeyboardSwitcher mKeyboardSwitcher; private final SubtypeState mSubtypeState = new SubtypeState(); private final EmojiAltPhysicalKeyDetector mEmojiAltPhysicalKeyDetector = new EmojiAltPhysicalKeyDetector(mInputLogic.mConnection); private EmojiAltPhysicalKeyDetector mEmojiAltPhysicalKeyDetector; private StatsUtilsManager mStatsUtilsManager; // Working variable for {@link #startShowingInputView()} and // {@link #onEvaluateInputViewShown()}. Loading Loading @@ -702,6 +701,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mInputLogic.recycle(); } private boolean isImeSuppressedByHardwareKeyboard() { final KeyboardSwitcher switcher = KeyboardSwitcher.getInstance(); return switcher.isImeSuppressedByHardwareKeyboard( mSettings.getCurrent(), switcher.getKeyboardSwitchState()); } @Override public void onConfigurationChanged(final Configuration conf) { SettingsValues settingsValues = mSettings.getCurrent(); Loading @@ -716,7 +721,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // have a change in hardware keyboard configuration. loadSettings(); settingsValues = mSettings.getCurrent(); if (settingsValues.mHasHardwareKeyboard) { if (isImeSuppressedByHardwareKeyboard()) { // We call cleanupInternalStateForFinishInput() because it's the right thing to do; // however, it seems at the moment the framework is passing us a seemingly valid // but actually non-functional InputConnection object. So if this bug ever gets Loading Loading @@ -874,7 +879,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // can go into the correct mode, so we need to do some housekeeping here. final boolean needToCallLoadKeyboardLater; final Suggest suggest = mInputLogic.mSuggest; if (!currentSettingsValues.mHasHardwareKeyboard) { if (!isImeSuppressedByHardwareKeyboard()) { // The app calling setText() has the effect of clearing the composing // span, so we should reset our state unconditionally, even if restarting is true. // We also tell the input logic about the combining rules for the current subtype, so Loading Loading @@ -1118,8 +1123,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen return; } final int inputHeight = mInputView.getHeight(); final boolean hasHardwareKeyboard = settingsValues.mHasHardwareKeyboard; if (hasHardwareKeyboard && visibleKeyboardView.getVisibility() == View.GONE) { if (isImeSuppressedByHardwareKeyboard() && !visibleKeyboardView.isShown()) { // If there is a hardware keyboard and a visible software keyboard view has been hidden, // no visual element will be shown on the screen. outInsets.contentTopInsets = inputHeight; Loading Loading @@ -1165,7 +1169,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen @Override public boolean onShowInputRequested(final int flags, final boolean configChange) { if (Settings.getInstance().getCurrent().mHasHardwareKeyboard) { if (isImeSuppressedByHardwareKeyboard()) { return true; } return super.onShowInputRequested(flags, configChange); Loading @@ -1182,7 +1186,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen @Override public boolean onEvaluateFullscreenMode() { final SettingsValues settingsValues = mSettings.getCurrent(); if (settingsValues.mHasHardwareKeyboard) { if (isImeSuppressedByHardwareKeyboard()) { // If there is a hardware keyboard, disable full screen mode. return false; } Loading Loading @@ -1646,8 +1650,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Hooks for hardware keyboard @Override public boolean onKeyDown(final int keyCode, final KeyEvent keyEvent) { // TODO: This should be processed in {@link InputLogic}. mEmojiAltPhysicalKeyDetector.onKeyDown(keyEvent); if (!ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED) { return super.onKeyDown(keyCode, keyEvent); } Loading @@ -1668,7 +1670,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen @Override public boolean onKeyUp(final int keyCode, final KeyEvent keyEvent) { // TODO: This should be processed in {@link InputLogic}. if (mEmojiAltPhysicalKeyDetector == null) { mEmojiAltPhysicalKeyDetector = new EmojiAltPhysicalKeyDetector( getApplicationContext().getResources()); } mEmojiAltPhysicalKeyDetector.onKeyUp(keyEvent); if (!ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED) { return super.onKeyUp(keyCode, keyEvent); Loading