Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Chat State Notifications support for group chat #352

Draft
wants to merge 1 commit into
base: 4.3
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,23 @@
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.chat2.Chat;
import org.jivesoftware.smack.chat2.ChatManager;
import org.jivesoftware.smack.chat2.OutgoingChatMessageListener;
import org.jivesoftware.smack.filter.AndFilter;
import org.jivesoftware.smack.filter.FromTypeFilter;
import org.jivesoftware.smack.filter.MessageTypeFilter;
import org.jivesoftware.smack.filter.MessageWithBodiesFilter;
import org.jivesoftware.smack.filter.NotFilter;
import org.jivesoftware.smack.filter.OrFilter;
import org.jivesoftware.smack.filter.StanzaExtensionFilter;
import org.jivesoftware.smack.filter.StanzaFilter;
import org.jivesoftware.smack.filter.StanzaTypeFilter;
import org.jivesoftware.smack.filter.ToTypeFilter;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smackx.chatstates.packet.ChatStateExtension;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.muc.MultiUserChat;
import org.jivesoftware.smackx.muc.MultiUserChatManager;
import org.jivesoftware.smackx.xhtmlim.packet.XHTMLExtension;

import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.EntityFullJid;
Expand All @@ -60,6 +65,7 @@
*
* @author Alexander Wenckus
* @author Paul Schaub
* @author Eng ChongMeng
* @see org.jivesoftware.smackx.chatstates.ChatState
* @see org.jivesoftware.smackx.chatstates.packet.ChatStateExtension
*/
Expand All @@ -72,19 +78,28 @@ public final class ChatStateManager extends Manager {
private static final Map<XMPPConnection, ChatStateManager> INSTANCES = new WeakHashMap<>();

private static final StanzaFilter filter = new NotFilter(new StanzaExtensionFilter(NAMESPACE));
private static final StanzaFilter INCOMING_MESSAGE_FILTER =
new AndFilter(MessageTypeFilter.NORMAL_OR_CHAT, FromTypeFilter.ENTITY_FULL_JID);
private static final StanzaFilter INCOMING_CHAT_STATE_FILTER = new AndFilter(INCOMING_MESSAGE_FILTER, new StanzaExtensionFilter(NAMESPACE));
private static final StanzaFilter INCOMING_CHAT_STATE_FILTER =
new AndFilter(StanzaTypeFilter.MESSAGE, new StanzaExtensionFilter(NAMESPACE)
);

private static final StanzaFilter MESSAGE_FILTER = new AndFilter(
new OrFilter(MessageTypeFilter.NORMAL_OR_CHAT, MessageTypeFilter.GROUPCHAT),
new OrFilter(MessageWithBodiesFilter.INSTANCE, new StanzaExtensionFilter(XHTMLExtension.ELEMENT, XHTMLExtension.NAMESPACE))
);

private static final StanzaFilter OUTGOING_MESSAGE_FILTER =
new AndFilter( MESSAGE_FILTER, ToTypeFilter.ENTITY_FULL_OR_BARE_JID
);

/**
* Registered ChatStateListeners
*/
private final Set<ChatStateListener> chatStateListeners = new HashSet<>();

/**
* Maps chat to last chat state.
* Maps chat/mucChat to last chat state.
*/
private final Map<Chat, ChatState> chatStates = new WeakHashMap<>();
private final Map<Object, ChatState> chatStates = new WeakHashMap<>();

private final AsyncButOrdered<Chat> asyncButOrdered = new AsyncButOrdered<>();

Expand Down Expand Up @@ -113,24 +128,32 @@ public static synchronized ChatStateManager getInstance(final XMPPConnection con
private ChatStateManager(XMPPConnection connection) {
super(connection);
ChatManager chatManager = ChatManager.getInstanceFor(connection);
chatManager.addOutgoingListener(new OutgoingChatMessageListener() {
MultiUserChatManager multiUserChatManager = MultiUserChatManager.getInstanceFor(connection);

connection.addStanzaInterceptor(new StanzaListener() {
@Override
public void newOutgoingMessage(EntityBareJid to, Message message, Chat chat) {
if (chat == null) {
return;
}
public void processStanza(Stanza stanza) throws NotConnectedException, InterruptedException {
Message message = (Message) stanza;

// if message already has a chatStateExtension, then do nothing,
if (!filter.accept(message)) {
return;
}

Object chat;
EntityBareJid entityBareJid = message.getTo().asEntityBareJidIfPossible();
if (message.getType() == Message.Type.groupchat) {
chat = multiUserChatManager.getMultiUserChat(entityBareJid);
} else {
chat = chatManager.chatWith(entityBareJid);
}

// otherwise add a chatState extension if necessary.
if (updateChatState(chat, ChatState.active)) {
message.addExtension(new ChatStateExtension(ChatState.active));
}
}
});
}, OUTGOING_MESSAGE_FILTER);

connection.addSyncStanzaListener(new StanzaListener() {
@Override
Expand Down Expand Up @@ -199,7 +222,6 @@ public boolean removeChatStateListener(ChatStateListener listener) {
}
}


/**
* Sets the current state of the provided chat. This method will send an empty bodied Message
* stanza with the state attached as a {@link org.jivesoftware.smack.packet.ExtensionElement}, if
Expand All @@ -224,6 +246,29 @@ public void setCurrentState(ChatState newState, Chat chat) throws NotConnectedEx
chat.send(message);
}

/**
* Sets the current state of the provided mucchat. This method will send an empty bodied Message
* stanza with the state attached as a {@link org.jivesoftware.smack.packet.ExtensionElement}, if
* and only if the new chat state is different than the last state.
*
* @param newState the new state of the chat
* @param mucChat the MultiUserChat instance
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws InterruptedException if the calling thread was interrupted.
*/
public void setCurrentState(ChatState newState, MultiUserChat mucChat) throws NotConnectedException, InterruptedException {
if (mucChat == null || newState == null) {
throw new IllegalArgumentException("Arguments cannot be null.");
}
if (!updateChatState(mucChat, newState)) {
return;
}
Message message = new Message();
ChatStateExtension extension = new ChatStateExtension(newState);
message.addExtension(extension);

mucChat.sendMessage(message);
}

@Override
public boolean equals(Object o) {
Expand All @@ -241,13 +286,12 @@ public int hashCode() {
return connection().hashCode();
}

private synchronized boolean updateChatState(Chat chat, ChatState newState) {
private synchronized boolean updateChatState(Object chat, ChatState newState) {
ChatState lastChatState = chatStates.get(chat);
if (lastChatState != newState) {
chatStates.put(chat, newState);
return true;
}
return false;
}

}