Define shortcuts for toggling IME layouts.

Out of the box, we want Alt-Left to toggle Emojis, while Alt-Right
toggles the shifted symbols layout.

Bug: 23954008
Bug: 24369173

Change-Id: I93dd66fb469e5d0a831359ff3a786fe68e1d73ea
(cherry picked from commit 411841b374aa04e333ea5a438dfd539f49ec589a)
diff --git a/java/res/values/config.xml b/java/res/values/config.xml
index 40760f6..33bcb06 100644
--- a/java/res/values/config.xml
+++ b/java/res/values/config.xml
@@ -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>
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 92e5dfc..4d337b6 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -44,6 +44,8 @@
 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();
 
@@ -139,15 +141,18 @@
         }
     }
 
-    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);
@@ -161,9 +166,9 @@
                 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,
@@ -205,7 +210,7 @@
         if (DEBUG_ACTION) {
             Log.d(TAG, "setAlphabetKeyboard");
         }
-        setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET));
+        setKeyboard(KeyboardId.ELEMENT_ALPHABET, KeyboardSwitchState.OTHER);
     }
 
     // Implements {@link KeyboardState.SwitchActions}.
@@ -214,7 +219,7 @@
         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}.
@@ -223,7 +228,7 @@
         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}.
@@ -232,7 +237,7 @@
         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}.
@@ -241,7 +246,7 @@
         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}.
@@ -250,11 +255,29 @@
         if (DEBUG_ACTION) {
             Log.d(TAG, "setSymbolsKeyboard");
         }
-        setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS));
+        setKeyboard(KeyboardId.ELEMENT_SYMBOLS, KeyboardSwitchState.OTHER);
     }
 
-    private void setMainKeyboardFrame(final SettingsValues settingsValues) {
-        final int visibility = settingsValues.mHasHardwareKeyboard ? View.GONE : View.VISIBLE;
+    // 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(
+            @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
@@ -282,24 +305,55 @@
         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;
         }
-        setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_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);
+            }
+        }
     }
 
     // Future method for requesting an updating to the shift state.
@@ -355,6 +409,19 @@
         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();
     }
diff --git a/java/src/com/android/inputmethod/latin/EmojiAltPhysicalKeyDetector.java b/java/src/com/android/inputmethod/latin/EmojiAltPhysicalKeyDetector.java
index 2a92dae..2529424 100644
--- a/java/src/com/android/inputmethod/latin/EmojiAltPhysicalKeyDetector.java
+++ b/java/src/com/android/inputmethod/latin/EmojiAltPhysicalKeyDetector.java
@@ -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<>();
+    }
+
+    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);
+            }
+        }
+        return definition;
     }
 
     /**
-     * Record a down key event.
-     * @param keyEvent a down key event.
-     */
-    public void onKeyDown(final KeyEvent keyEvent) {
-        if (isAltKey(keyEvent)) {
-            mAltHasBeenUsedAsAModifier = false;
-        }
-        if (containsAltModifier(keyEvent)) {
-            mAltHasBeenUsedAsAModifier = true;
-        }
-    }
-
-    /**
-     * 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;
-            return;
-        }
-        if (!Settings.getInstance().getCurrent().mEnableEmojiAltPhysicalKey) {
-            return;
-        }
-        if (mAltHasBeenUsedAsAModifier) {
-            return;
-        }
-        onEmojiAltKeyDetected();
-    }
-
-    private static void onEmojiAltKeyDetected() {
-        KeyboardSwitcher.getInstance().onToggleEmojiKeyboard();
-    }
-
-    private static boolean isAltKey(final KeyEvent keyEvent) {
         final int keyCode = keyEvent.getKeyCode();
-        return keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT;
+        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 (!isMappedKeyCode(keyEvent)) {
+            // Nothing special about this key.
+            Log.d(TAG, "onKeyUp() : Not Mapped: " + keyCode);
+            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);
+        }
     }
 
-    private static boolean containsAltModifier(final KeyEvent keyEvent) {
+    /**
+     * @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;
+    }
+
+    /**
+     * @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();
         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;
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 1f2b6f2..25a5de2 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -142,8 +142,7 @@
     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()}.
@@ -702,6 +701,12 @@
         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();
@@ -716,7 +721,7 @@
             // 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
@@ -874,7 +879,7 @@
         // 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
@@ -1118,8 +1123,7 @@
             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;
@@ -1165,7 +1169,7 @@
 
     @Override
     public boolean onShowInputRequested(final int flags, final boolean configChange) {
-        if (Settings.getInstance().getCurrent().mHasHardwareKeyboard) {
+        if (isImeSuppressedByHardwareKeyboard()) {
             return true;
         }
         return super.onShowInputRequested(flags, configChange);
@@ -1182,7 +1186,7 @@
     @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;
         }
@@ -1646,8 +1650,6 @@
     // 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);
         }
@@ -1668,7 +1670,10 @@
 
     @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);