class XmppSessionIdleTask extends TimerTask {
        private Instant pendingPingSentAt = null;

        @Override
        public void run()
        {<FILL_FUNCTION_BODY>}

        private void sendPing()
        {
            // Ping the connection to see if it is alive.
            final JID entity = wsConnection.getStanzaHandler().getAddress();
            final IQ pingRequest = new IQ(IQ.Type.get);
            pingRequest.setChildElement("ping", IQPingHandler.NAMESPACE);
            pingRequest.setFrom(XMPPServer.getInstance().getServerInfo().getXMPPDomain());
            pingRequest.setTo(entity);

            Log.debug("Pinging websocket connection (XMPP address: '{}') that has been idle: {}", entity, wsConnection);

            // OF-1497: Ensure that data sent to the client is processed through LocalClientSession, to avoid
            // synchronisation issues with stanza counts related to Stream Management (XEP-0198)!
            final LocalClientSession ofSession = (LocalClientSession) SessionManager.getInstance().getSession(entity);
            if (ofSession == null) {
                Log.warn( "Trying to ping a websocket connection (XMPP address: '{}') that's idle, but has no corresponding Openfire session. Websocket connection: {}", entity, wsConnection );
            } else {
                try {
                    ofSession.deliver( pingRequest );
                    pendingPingSentAt = Instant.now();
                } catch (UnauthorizedException e) {
                    Log.warn("An unexpected exception occurred while trying to ping a websocket connection (XMPP address: '{}') that's idle. Websocket connection: {}", entity, wsConnection, e);
                }
            }
        }
    }

            if (!isWebSocketOpen() || getMaxIdleTime().isNegative() || getMaxIdleTime().isZero()) {
                TaskEngine.getInstance().cancelScheduledTask(websocketFramePingTask);
                TaskEngine.getInstance().cancelScheduledTask(xmppSessionIdleTask);
                return;
            }

            // Was there activity since we've pinged, then reset the 'ping' status.
            if (pendingPingSentAt != null && lastReceived.isAfter(pendingPingSentAt)) {
                pendingPingSentAt = null;
            }

            final Duration inactive = Duration.between(lastReceived, Instant.now());
            if (inactive.compareTo(getMaxIdleTime()) > 0) {
                // Inactive session, that didn't respond to a ping that should've been sent earlier. Kill.
                Log.debug("Closing connection that has been idle: {}", wsConnection);
                wsConnection.close(new StreamError(StreamError.Condition.connection_timeout, "Closing connection due to inactivity."));
                return;
            }

            final boolean doPing = ConnectionSettings.Client.KEEP_ALIVE_PING_PROPERTY.getValue();
            if (doPing) {
                // Probe the peer once, when it's inactive for 50% of the maximum idle time (this mimics the behavior for TCP connections).
                final boolean alreadyPinged = pendingPingSentAt != null;
                if (!alreadyPinged) {
                    if (inactive.compareTo(getMaxIdleTime().dividedBy(2)) > 0) {
                        // Client has been inactive for more than 50% of the max idle time, and has not been pinged yet.
                        sendPing();
                    }
                }
            }
        
<no_super_class>