@Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent && ((IdleStateEvent) evt).state() == IdleState.READER_IDLE) {
            final boolean doPing = ConnectionSettings.Client.KEEP_ALIVE_PING_PROPERTY.getValue() && clientConnection;
            final Channel channel = ctx.channel();
            if (channel.attr(IDLE_FLAG).getAndSet(true) == null) {
                // Idle flag is now set, but wasn't present before.
                if (doPing) {
                    sendPingPacket(channel);
                }
            } else {
                // Idle flag already present. Connection has been idle for a while. Close it.
                final NettyConnection connection = channel.attr(CONNECTION).get();
                Log.debug("Closing connection because of inactivity: {}", connection);
                connection.close(new StreamError(StreamError.Condition.connection_timeout, doPing ? "Connection has been idle and did not respond to a keep-alive check." : "Connection has been idle."), doPing);
            }
        }
        super.userEventTriggered(ctx, evt);
    }
Processes IdleStateEvents triggered by an IdleStateHandler.
If the IdleStateEvent is an idle read state, the Netty channel is closed.
If the IdleStateEvent is an idle write state, an XMPP ping  request is sent
to the remote entity.

@param ctx ChannelHandlerContext
@param evt Event caught, expect IdleStateEvent
@throws Exception when attempting to deliver ping packet
xmppserver/src/main/java/org/jivesoftware/openfire/nio/NettyIdleStateKeepAliveHandler.java