mirror of
https://github.com/vanitasvitae/Smack.git
synced 2024-10-31 17:25:58 +01:00
Migrate the ChatState package to chat2 API
Fixes SMACK-761
This commit is contained in:
parent
f42d9137b5
commit
a3d430f334
5 changed files with 210 additions and 46 deletions
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -17,15 +17,16 @@
|
||||||
|
|
||||||
package org.jivesoftware.smackx.chatstates;
|
package org.jivesoftware.smackx.chatstates;
|
||||||
|
|
||||||
import org.jivesoftware.smack.chat.ChatMessageListener;
|
import org.jivesoftware.smack.chat2.Chat;
|
||||||
import org.jivesoftware.smack.packet.Message;
|
import org.jivesoftware.smack.packet.Message;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Events for when the state of a user in a chat changes.
|
* Events for when the state of a user in a chat changes.
|
||||||
*
|
*
|
||||||
* @author Alexander Wenckus
|
* @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.
|
* 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 state the new state of the participant.
|
||||||
* @param message the message carrying the chat state.
|
* @param message the message carrying the chat state.
|
||||||
*/
|
*/
|
||||||
// TODO Migrate to new chat2 API on Smack 4.3.
|
void stateChanged(Chat chat, ChatState state, Message message);
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
void stateChanged(org.jivesoftware.smack.chat.Chat chat, ChatState state, Message message);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -17,24 +17,33 @@
|
||||||
|
|
||||||
package org.jivesoftware.smackx.chatstates;
|
package org.jivesoftware.smackx.chatstates;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.WeakHashMap;
|
import java.util.WeakHashMap;
|
||||||
|
|
||||||
import org.jivesoftware.smack.Manager;
|
import org.jivesoftware.smack.Manager;
|
||||||
import org.jivesoftware.smack.MessageListener;
|
|
||||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||||
|
import org.jivesoftware.smack.StanzaListener;
|
||||||
import org.jivesoftware.smack.XMPPConnection;
|
import org.jivesoftware.smack.XMPPConnection;
|
||||||
import org.jivesoftware.smack.chat.ChatManagerListener;
|
import org.jivesoftware.smack.chat2.Chat;
|
||||||
import org.jivesoftware.smack.chat.ChatMessageListener;
|
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.NotFilter;
|
||||||
import org.jivesoftware.smack.filter.StanzaExtensionFilter;
|
import org.jivesoftware.smack.filter.StanzaExtensionFilter;
|
||||||
import org.jivesoftware.smack.filter.StanzaFilter;
|
import org.jivesoftware.smack.filter.StanzaFilter;
|
||||||
import org.jivesoftware.smack.packet.ExtensionElement;
|
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||||
import org.jivesoftware.smack.packet.Message;
|
import org.jivesoftware.smack.packet.Message;
|
||||||
|
import org.jivesoftware.smack.packet.Stanza;
|
||||||
import org.jivesoftware.smackx.chatstates.packet.ChatStateExtension;
|
import org.jivesoftware.smackx.chatstates.packet.ChatStateExtension;
|
||||||
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
|
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
|
* 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
|
* 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.
|
* If this does not occur you will not receive the update notifications.
|
||||||
*
|
*
|
||||||
* @author Alexander Wenckus
|
* @author Alexander Wenckus
|
||||||
|
* @author Paul Schaub
|
||||||
* @see org.jivesoftware.smackx.chatstates.ChatState
|
* @see org.jivesoftware.smackx.chatstates.ChatState
|
||||||
* @see org.jivesoftware.smackx.chatstates.packet.ChatStateExtension
|
* @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 final class ChatStateManager extends Manager {
|
||||||
|
|
||||||
public static final String NAMESPACE = "http://jabber.org/protocol/chatstates";
|
public static final String NAMESPACE = "http://jabber.org/protocol/chatstates";
|
||||||
|
|
||||||
private static final Map<XMPPConnection, ChatStateManager> INSTANCES = new WeakHashMap<>();
|
private static final Map<XMPPConnection, ChatStateManager> INSTANCES = new WeakHashMap<>();
|
||||||
|
|
||||||
private static final StanzaFilter filter = new NotFilter(new StanzaExtensionFilter(NAMESPACE));
|
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<ChatStateListener> chatStateListeners = new HashSet<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps chat to last chat state.
|
||||||
|
*/
|
||||||
|
private final Map<Chat, ChatState> chatStates = new WeakHashMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the ChatStateManager related to the XMPPConnection and it will create one if it does
|
* 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;
|
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<org.jivesoftware.smack.chat.Chat, ChatState> chatStates = new WeakHashMap<>();
|
|
||||||
|
|
||||||
private final org.jivesoftware.smack.chat.ChatManager chatManager;
|
|
||||||
|
|
||||||
private ChatStateManager(XMPPConnection connection) {
|
private ChatStateManager(XMPPConnection connection) {
|
||||||
super(connection);
|
super(connection);
|
||||||
chatManager = org.jivesoftware.smack.chat.ChatManager.getInstanceFor(connection);
|
ChatManager chatManager = ChatManager.getInstanceFor(connection);
|
||||||
chatManager.addOutgoingMessageInterceptor(outgoingInterceptor, filter);
|
chatManager.addOutgoingListener(outgoingInterceptor);
|
||||||
chatManager.addChatListener(incomingInterceptor);
|
connection.addAsyncStanzaListener(new IncomingMessageInterceptor(),
|
||||||
|
new AndFilter(INCOMING_MESSAGE_FILTER, new StanzaExtensionFilter(NAMESPACE)));
|
||||||
|
|
||||||
ServiceDiscoveryManager.getInstanceFor(connection).addFeature(NAMESPACE);
|
ServiceDiscoveryManager.getInstanceFor(connection).addFeature(NAMESPACE);
|
||||||
INSTANCES.put(connection, this);
|
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
|
* Sets the current state of the provided chat. This method will send an empty bodied Message
|
||||||
|
@ -104,7 +151,7 @@ public final class ChatStateManager extends Manager {
|
||||||
* @throws NotConnectedException
|
* @throws NotConnectedException
|
||||||
* @throws InterruptedException
|
* @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) {
|
if (chat == null || newState == null) {
|
||||||
throw new IllegalArgumentException("Arguments cannot be null.");
|
throw new IllegalArgumentException("Arguments cannot be null.");
|
||||||
}
|
}
|
||||||
|
@ -115,7 +162,7 @@ public final class ChatStateManager extends Manager {
|
||||||
ChatStateExtension extension = new ChatStateExtension(newState);
|
ChatStateExtension extension = new ChatStateExtension(newState);
|
||||||
message.addExtension(extension);
|
message.addExtension(extension);
|
||||||
|
|
||||||
chat.sendMessage(message);
|
chat.send(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -135,7 +182,7 @@ public final class ChatStateManager extends Manager {
|
||||||
return connection().hashCode();
|
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);
|
ChatState lastChatState = chatStates.get(chat);
|
||||||
if (lastChatState != newState) {
|
if (lastChatState != newState) {
|
||||||
chatStates.put(chat, newState);
|
chatStates.put(chat, newState);
|
||||||
|
@ -144,38 +191,44 @@ public final class ChatStateManager extends Manager {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void fireNewChatState(org.jivesoftware.smack.chat.Chat chat, ChatState state, Message message) {
|
private void fireNewChatState(Chat chat, ChatState state, Message message) {
|
||||||
for (ChatMessageListener listener : chat.getListeners()) {
|
for (ChatStateListener listener : chatStateListeners) {
|
||||||
if (listener instanceof ChatStateListener) {
|
listener.stateChanged(chat, state, message);
|
||||||
((ChatStateListener) listener).stateChanged(chat, state, message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class OutgoingMessageInterceptor implements MessageListener {
|
private class OutgoingMessageInterceptor implements OutgoingChatMessageListener {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void processMessage(Message message) {
|
public void newOutgoingMessage(EntityBareJid to, Message message, Chat chat) {
|
||||||
org.jivesoftware.smack.chat.Chat chat = chatManager.getThreadChat(message.getThread());
|
|
||||||
if (chat == null) {
|
if (chat == null) {
|
||||||
return;
|
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)) {
|
if (updateChatState(chat, ChatState.active)) {
|
||||||
message.addExtension(new ChatStateExtension(ChatState.active));
|
message.addExtension(new ChatStateExtension(ChatState.active));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class IncomingMessageInterceptor implements ChatManagerListener, ChatMessageListener {
|
private class IncomingMessageInterceptor implements StanzaListener {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void chatCreated(final org.jivesoftware.smack.chat.Chat chat, boolean createdLocally) {
|
public void processStanza(Stanza packet) {
|
||||||
chat.addMessageListener(this);
|
Message message = (Message) packet;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
EntityFullJid fullFrom = message.getFrom().asEntityFullJidIfPossible();
|
||||||
public void processMessage(org.jivesoftware.smack.chat.Chat chat, Message message) {
|
EntityBareJid bareFrom = fullFrom.asEntityBareJid();
|
||||||
|
|
||||||
|
Chat chat = ChatManager.getInstanceFor(connection()).chatWith(bareFrom);
|
||||||
ExtensionElement extension = message.getExtension(NAMESPACE);
|
ExtensionElement extension = message.getExtension(NAMESPACE);
|
||||||
|
|
||||||
if (extension == null) {
|
if (extension == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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 (<a href="http://www.xmpp.org/extensions/xep-0085.html">XEP-0085</a>).
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.chatstates;
|
package org.jivesoftware.smackx.chatstates;
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
Loading…
Reference in a new issue