private boolean processKey(int keyCode, KeyEvent event, boolean down) {
    if (GamepadUtils.isSonyXperiaGamepadKeyEvent(event)) {
      event = GamepadUtils.translateSonyXperiaGamepadKeys(keyCode, event);
      keyCode = event.getKeyCode();
    }

    if (keyCode > KeyEvent.getMaxKeyCode() || !shouldProcessKey(keyCode, event)) {
      return false;
    }
    event = translateKey(keyCode, event);
    keyCode = event.getKeyCode();

    View view = getView();
    if (view == null) {
      InputThreadUtils.sInstance.sendEventFromUiThread(
          ThreadUtils.getUiHandler(), mEditableClient, GoannaEvent.createKeyEvent(event, 0));
      return true;
    }

    // KeyListener returns true if it handled the event for us. KeyListener is only
    // safe to use on the UI thread; therefore we need to pass a proxy Editable to it
    KeyListener keyListener = TextKeyListener.getInstance();
    Handler uiHandler = view.getRootView().getHandler();
    Editable uiEditable =
        InputThreadUtils.sInstance.getEditableForUiThread(uiHandler, mEditableClient);
    boolean skip = shouldSkipKeyListener(keyCode, event);
    if (down) {
      mEditableClient.setSuppressKeyUp(true);
    }
    if (skip
        || (down && !keyListener.onKeyDown(view, uiEditable, keyCode, event))
        || (!down && !keyListener.onKeyUp(view, uiEditable, keyCode, event))) {
      InputThreadUtils.sInstance.sendEventFromUiThread(
          uiHandler,
          mEditableClient,
          GoannaEvent.createKeyEvent(event, TextKeyListener.getMetaState(uiEditable)));
      if (skip && down) {
        // Usually, the down key listener call above adjusts meta states for us.
        // However, if we skip that call above, we have to manually adjust meta
        // states so the meta states remain consistent
        TextKeyListener.adjustMetaAfterKeypress(uiEditable);
      }
    }
    if (down) {
      mEditableClient.setSuppressKeyUp(false);
    }
    return true;
  }