diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/chatstates/ChatStateListener.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/chatstates/ChatStateListener.java index c670f55ac..a3bbb0cd4 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/chatstates/ChatStateListener.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/chatstates/ChatStateListener.java @@ -1,6 +1,6 @@ /** * - * Copyright 2003-2007 Jive Software. + * Copyright 2003-2007 Jive Software, 2018 Paul Schaub. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,15 +17,16 @@ package org.jivesoftware.smackx.chatstates; -import org.jivesoftware.smack.chat.ChatMessageListener; +import org.jivesoftware.smack.chat2.Chat; import org.jivesoftware.smack.packet.Message; /** * Events for when the state of a user in a chat changes. * * @author Alexander Wenckus + * @author Paul Schaub */ -public interface ChatStateListener extends ChatMessageListener { +public interface ChatStateListener { /** * Fired when the state of a chat with another user changes. @@ -34,7 +35,5 @@ public interface ChatStateListener extends ChatMessageListener { * @param state the new state of the participant. * @param message the message carrying the chat state. */ - // TODO Migrate to new chat2 API on Smack 4.3. - @SuppressWarnings("deprecation") - void stateChanged(org.jivesoftware.smack.chat.Chat chat, ChatState state, Message message); + void stateChanged(Chat chat, ChatState state, Message message); } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/chatstates/ChatStateManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/chatstates/ChatStateManager.java index 8e280dd0e..b5ec8b0f8 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/chatstates/ChatStateManager.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/chatstates/ChatStateManager.java @@ -1,6 +1,6 @@ /** * - * Copyright 2003-2007 Jive Software. + * Copyright 2003-2007 Jive Software, 2018 Paul Schaub. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,24 +17,33 @@ package org.jivesoftware.smackx.chatstates; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import java.util.WeakHashMap; import org.jivesoftware.smack.Manager; -import org.jivesoftware.smack.MessageListener; import org.jivesoftware.smack.SmackException.NotConnectedException; +import org.jivesoftware.smack.StanzaListener; import org.jivesoftware.smack.XMPPConnection; -import org.jivesoftware.smack.chat.ChatManagerListener; -import org.jivesoftware.smack.chat.ChatMessageListener; +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.NotFilter; import org.jivesoftware.smack.filter.StanzaExtensionFilter; import org.jivesoftware.smack.filter.StanzaFilter; 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.jxmpp.jid.EntityBareJid; +import org.jxmpp.jid.EntityFullJid; + /** * Handles chat state for all chats on a particular XMPPConnection. This class manages both the * stanza(/packet) extensions and the disco response necessary for compliance with @@ -45,17 +54,39 @@ import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; * If this does not occur you will not receive the update notifications. * * @author Alexander Wenckus + * @author Paul Schaub * @see org.jivesoftware.smackx.chatstates.ChatState * @see org.jivesoftware.smackx.chatstates.packet.ChatStateExtension */ -// TODO Migrate to new chat2 API on Smack 4.3. -@SuppressWarnings("deprecation") public final class ChatStateManager extends Manager { + public static final String NAMESPACE = "http://jabber.org/protocol/chatstates"; private static final Map 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); + + /** + * Message listener, that appends a ChatStateExtension to outgoing messages + */ + private final OutgoingMessageInterceptor outgoingInterceptor = new OutgoingMessageInterceptor(); + + /** + * Message listener, that triggers registered ChatStateListeners for incoming chat messages with ChatStateExtensions. + */ + private final IncomingMessageInterceptor incomingInterceptor = new IncomingMessageInterceptor(); + + /** + * Registered ChatStateListeners + */ + private final Set chatStateListeners = new HashSet<>(); + + /** + * Maps chat to last chat state. + */ + private final Map chatStates = new WeakHashMap<>(); /** * Returns the ChatStateManager related to the XMPPConnection and it will create one if it does @@ -72,27 +103,43 @@ public final class ChatStateManager extends Manager { return manager; } - private final OutgoingMessageInterceptor outgoingInterceptor = new OutgoingMessageInterceptor(); - - private final IncomingMessageInterceptor incomingInterceptor = new IncomingMessageInterceptor(); - /** - * Maps chat to last chat state. + * Private constructor to create a new ChatStateManager. + * This adds ChatMessageListeners as interceptors to the connection and adds the namespace to the disco features. + * + * @param connection xmpp connection */ - private final Map chatStates = new WeakHashMap<>(); - - private final org.jivesoftware.smack.chat.ChatManager chatManager; - private ChatStateManager(XMPPConnection connection) { super(connection); - chatManager = org.jivesoftware.smack.chat.ChatManager.getInstanceFor(connection); - chatManager.addOutgoingMessageInterceptor(outgoingInterceptor, filter); - chatManager.addChatListener(incomingInterceptor); + ChatManager chatManager = ChatManager.getInstanceFor(connection); + chatManager.addOutgoingListener(outgoingInterceptor); + connection.addAsyncStanzaListener(new IncomingMessageInterceptor(), + new AndFilter(INCOMING_MESSAGE_FILTER, new StanzaExtensionFilter(NAMESPACE))); ServiceDiscoveryManager.getInstanceFor(connection).addFeature(NAMESPACE); INSTANCES.put(connection, this); } + /** + * Register a ChatStateListener. That listener will be informed about changed chat states. + * + * @param listener chatStateListener + * @return true, if the listener was not registered before + */ + public boolean addChatStateListener(ChatStateListener listener) { + return chatStateListeners.add(listener); + } + + /** + * Unregister a ChatStateListener. + * + * @param listener chatStateListener + * @return true, if the listener was registered before + */ + public boolean removeChatStateListener(ChatStateListener listener) { + return chatStateListeners.remove(listener); + } + /** * Sets the current state of the provided chat. This method will send an empty bodied Message @@ -101,10 +148,10 @@ public final class ChatStateManager extends Manager { * * @param newState the new state of the chat * @param chat the chat. - * @throws NotConnectedException - * @throws InterruptedException + * @throws NotConnectedException + * @throws InterruptedException */ - public void setCurrentState(ChatState newState, org.jivesoftware.smack.chat.Chat chat) throws NotConnectedException, InterruptedException { + public void setCurrentState(ChatState newState, Chat chat) throws NotConnectedException, InterruptedException { if (chat == null || newState == null) { throw new IllegalArgumentException("Arguments cannot be null."); } @@ -115,7 +162,7 @@ public final class ChatStateManager extends Manager { ChatStateExtension extension = new ChatStateExtension(newState); message.addExtension(extension); - chat.sendMessage(message); + chat.send(message); } @@ -135,7 +182,7 @@ public final class ChatStateManager extends Manager { return connection().hashCode(); } - private synchronized boolean updateChatState(org.jivesoftware.smack.chat.Chat chat, ChatState newState) { + private synchronized boolean updateChatState(Chat chat, ChatState newState) { ChatState lastChatState = chatStates.get(chat); if (lastChatState != newState) { chatStates.put(chat, newState); @@ -144,38 +191,44 @@ public final class ChatStateManager extends Manager { return false; } - private static void fireNewChatState(org.jivesoftware.smack.chat.Chat chat, ChatState state, Message message) { - for (ChatMessageListener listener : chat.getListeners()) { - if (listener instanceof ChatStateListener) { - ((ChatStateListener) listener).stateChanged(chat, state, message); - } + private void fireNewChatState(Chat chat, ChatState state, Message message) { + for (ChatStateListener listener : chatStateListeners) { + listener.stateChanged(chat, state, message); } } - private class OutgoingMessageInterceptor implements MessageListener { + private class OutgoingMessageInterceptor implements OutgoingChatMessageListener { @Override - public void processMessage(Message message) { - org.jivesoftware.smack.chat.Chat chat = chatManager.getThreadChat(message.getThread()); + public void newOutgoingMessage(EntityBareJid to, Message message, Chat chat) { if (chat == null) { return; } + + // if message already has a chatStateExtension, then do nothing, + if (!filter.accept(message)) { + return; + } + + // otherwise add a chatState extension if necessary. if (updateChatState(chat, ChatState.active)) { message.addExtension(new ChatStateExtension(ChatState.active)); } } } - private static class IncomingMessageInterceptor implements ChatManagerListener, ChatMessageListener { + private class IncomingMessageInterceptor implements StanzaListener { @Override - public void chatCreated(final org.jivesoftware.smack.chat.Chat chat, boolean createdLocally) { - chat.addMessageListener(this); - } + public void processStanza(Stanza packet) { + Message message = (Message) packet; - @Override - public void processMessage(org.jivesoftware.smack.chat.Chat chat, Message message) { + EntityFullJid fullFrom = message.getFrom().asEntityFullJidIfPossible(); + EntityBareJid bareFrom = fullFrom.asEntityBareJid(); + + Chat chat = ChatManager.getInstanceFor(connection()).chatWith(bareFrom); ExtensionElement extension = message.getExtension(NAMESPACE); + if (extension == null) { return; } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/chatstates/package-info.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/chatstates/package-info.java index 049af49ff..6257be95e 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/chatstates/package-info.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/chatstates/package-info.java @@ -1,6 +1,6 @@ /** * - * Copyright 2015 Florian Schmaus + * Copyright 2015 - 2018 Florian Schmaus, Paul Schaub * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,6 @@ */ /** - * Smacks implementation of XEP-0085: Chat State Notifications. + * Classes for Chat States (XEP-0085). */ package org.jivesoftware.smackx.chatstates; diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/chatstate/ChatStateIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/chatstate/ChatStateIntegrationTest.java new file mode 100644 index 000000000..97133ff06 --- /dev/null +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/chatstate/ChatStateIntegrationTest.java @@ -0,0 +1,91 @@ +/** + * + * Copyright 2018 Paul Schaub + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smackx.chatstate; + +import org.jivesoftware.smack.chat2.Chat; +import org.jivesoftware.smack.chat2.ChatManager; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smackx.chatstates.ChatState; +import org.jivesoftware.smackx.chatstates.ChatStateListener; +import org.jivesoftware.smackx.chatstates.ChatStateManager; + +import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest; +import org.igniterealtime.smack.inttest.SmackIntegrationTest; +import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment; +import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint; +import org.junit.After; + +public class ChatStateIntegrationTest extends AbstractSmackIntegrationTest { + + // Listener for composing chat state + private final SimpleResultSyncPoint composingSyncPoint = new SimpleResultSyncPoint(); + private final ChatStateListener composingListener = new ChatStateListener() { + @Override + public void stateChanged(Chat chat, ChatState state, Message message) { + if (state.equals(ChatState.composing)) { + composingSyncPoint.signal(); + } + } + }; + + // Listener for active chat state + private final SimpleResultSyncPoint activeSyncPoint = new SimpleResultSyncPoint(); + private final ChatStateListener activeListener = new ChatStateListener() { + @Override + public void stateChanged(Chat chat, ChatState state, Message message) { + if (state.equals(ChatState.active)) { + activeSyncPoint.signal(); + } + } + }; + + + public ChatStateIntegrationTest(SmackIntegrationTestEnvironment environment) { + super(environment); + } + + @SmackIntegrationTest + public void testChatStateListeners() throws Exception { + ChatStateManager manOne = ChatStateManager.getInstance(conOne); + ChatStateManager manTwo = ChatStateManager.getInstance(conTwo); + + // Add chatState listeners. + manTwo.addChatStateListener(composingListener); + manTwo.addChatStateListener(activeListener); + + Chat chatOne = ChatManager.getInstanceFor(conOne) + .chatWith(conTwo.getUser().asEntityBareJid()); + + // Test, if setCurrentState works and the chatState arrives + manOne.setCurrentState(ChatState.composing, chatOne); + composingSyncPoint.waitForResult(timeout); + + // Test, if the OutgoingMessageInterceptor successfully adds a chatStateExtension of "active" to + // an outgoing chat message and if it arrives at the other side. + Chat chat = ChatManager.getInstanceFor(conOne) + .chatWith(conTwo.getUser().asEntityBareJid()); + chat.send("Hi!"); + activeSyncPoint.waitForResult(timeout); + } + + @After + public void cleanup() { + ChatStateManager manTwo = ChatStateManager.getInstance(conTwo); + manTwo.removeChatStateListener(composingListener); + manTwo.removeChatStateListener(activeListener); + } +} diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/chatstate/package-info.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/chatstate/package-info.java new file mode 100644 index 000000000..e9e2f12f4 --- /dev/null +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/chatstate/package-info.java @@ -0,0 +1,21 @@ +/** + * + * Copyright 2018 Paul Schaub + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * TODO describe me. + */ +package org.jivesoftware.smackx.chatstate;