rocks.xmpp.extensions.chatstates.ChatStateManager Maven / Gradle / Ivy
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2016 Christian Schudt
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package rocks.xmpp.extensions.chatstates;
import rocks.xmpp.addr.Jid;
import rocks.xmpp.core.session.Manager;
import rocks.xmpp.core.session.XmppSession;
import rocks.xmpp.core.stanza.MessageEvent;
import rocks.xmpp.core.stanza.model.Message;
import rocks.xmpp.extensions.chatstates.model.ChatState;
import rocks.xmpp.extensions.xhtmlim.model.Html;
import rocks.xmpp.im.chat.Chat;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
/**
* This class manages Chat State Notifications, which are used to communicate the status of a user in a chat session, thus indicating whether a chat partner is actively engaged in the chat, composing a message, temporarily paused, inactive, or gone.
* Chat states can be used in the context of a one-to-one chat session or a multi-user chat room.
*
* Because the Chat State protocol is relatively simple, the primary purpose of this manager is to enable or disable the Chat State protocol for Service Discovery purposes.
*
*
* Furthermore it ensures that every sent message has a chat state notification as required by XEP-0085.
*
* Sending Chat States
* Setting your own chat state can either be done in a one-to-one chat session or a group chat.
*
* {@code
* ChatStateManager chatStateManager = xmppSession.getManager(ChatStateManager.class);
* chatStateManager.setChatState(ChatState.COMPOSING, chat);
* }
*
* Receiving Chat States
* If you want to react to chat states of your chat partner(s), just check for chat state extension and deal with it accordingly.
*
* {@code
* ChatState chatState = message.getExtension(ChatState.class);
* if (chatState == ChatState.COMPOSING) {
* // Contact is typing.
* } else if (chatState == ChatState.PAUSED) {
* // Contact has paused typing.
* }
* }
*
*
* @author Christian Schudt
* @see XEP-0085: Chat State Notifications
*/
public final class ChatStateManager extends Manager {
private final Map chatMap = new ConcurrentHashMap<>();
private final Map contactSupportsChatStateNotifications = new ConcurrentHashMap<>();
private final Consumer messageListener;
private ChatStateManager(final XmppSession xmppSession) {
super(xmppSession, true);
this.messageListener = e -> {
Message message = e.getMessage();
// This protocol SHOULD NOT be used with message types other than "chat" or "groupchat".
if (message.getType() == Message.Type.CHAT || message.getType() == Message.Type.GROUPCHAT) {
// For outbound messages append .
boolean containsChatState = message.hasExtension(ChatState.class);
if (!e.isInbound()) {
// Append an chat state to every outbound content message (with or extension), if it doesn't contain a chat state yet
// and the recipient supports chat states or it is unknown if he supports them.
if (!containsChatState && (message.getBody() != null && !message.getBody().trim().equals("") || message.hasExtension(Html.class))) {
// If either support of chat states is unknown (== null) or it's known to be supported (== true), include an active chat state.
// (1. If the User desires chat state notifications, the message(s) that it sends to the Contact before receiving a reply MUST contain a chat state notification extension, which SHOULD be .)
Boolean isSupportedByPeer = contactSupportsChatStateNotifications.get(message.getTo());
if (isSupportedByPeer == null || isSupportedByPeer) {
message.addExtension(ChatState.ACTIVE);
}
}
} else if (message.getType() != Message.Type.GROUPCHAT) {
// Check if the contact supports chat states and update the map. If it does, it must include a chat state extension:
// 2. If the Contact replies but does not include a chat state notification extension, the User MUST NOT send subsequent chat state notifications to the Contact.
// 3. If the Contact replies and includes an notification (or sends a standalone notification to the User), the User and Contact SHOULD send subsequent notifications
contactSupportsChatStateNotifications.put(message.getFrom(), containsChatState);
}
}
};
}
@Override
protected void onEnable() {
super.onEnable();
xmppSession.addInboundMessageListener(messageListener);
xmppSession.addOutboundMessageListener(messageListener);
}
@Override
protected void onDisable() {
super.onDisable();
xmppSession.removeInboundMessageListener(messageListener);
xmppSession.removeOutboundMessageListener(messageListener);
}
/**
* Sets the chat state for a chat. If this manager is disabled this method has no effect.
*
* @param chatState The chat state.
* @param chat The chat.
* @return True, if the chat state has been sent; false, if it has not been sent (e.g. because it is known that the chat partner does not support chat states).
*/
public final boolean setChatState(ChatState chatState, Chat chat) {
if (!isEnabled()) {
throw new IllegalStateException("Chat States aren't enabled. Please enable them before sending chat states.");
}
// Avoid repetition.
// See XEP-0085 § 5.3 Repetition
if (chatMap.put(Objects.requireNonNull(chat), Objects.requireNonNull(chatState)) == chatState) {
return false;
}
Message message = new Message();
message.addExtension(chatState);
chat.sendMessage(message);
return true;
}
@Override
protected void dispose() {
chatMap.clear();
contactSupportsChatStateNotifications.clear();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy