From 665fc753249c848637adb7f5d44a68b77d4c642e Mon Sep 17 00:00:00 2001 From: Tarun Gupta Date: Sun, 28 Jun 2015 20:02:23 +0530 Subject: Add ChatStateNotifier. Adds ChatStateNotifier, DummyEntityCapsProvider. License: This patch is BSD-licensed, see Documentation/Licenses/BSD-simplified.txt for details. Test-Information: Test added for Chate State Notifier, which passes. Change-Id: Ie596d9c226526ab5ace334b7926389b61ca5540a diff --git a/src/com/isode/stroke/chat/ChatStateNotifier.java b/src/com/isode/stroke/chat/ChatStateNotifier.java new file mode 100644 index 0000000..dc96565 --- /dev/null +++ b/src/com/isode/stroke/chat/ChatStateNotifier.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2010 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ +/* + * Copyright (c) 2015 Tarun Gupta. + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +package com.isode.stroke.chat; + +import com.isode.stroke.elements.Message; +import com.isode.stroke.elements.ChatState; +import com.isode.stroke.elements.DiscoInfo; +import com.isode.stroke.disco.EntityCapsProvider; +import com.isode.stroke.client.StanzaChannel; +import com.isode.stroke.signals.SignalConnection; +import com.isode.stroke.signals.Slot1; +import com.isode.stroke.jid.JID; + +public class ChatStateNotifier { + + private StanzaChannel stanzaChannel_; + private EntityCapsProvider entityCapsManager_; + private JID contact_ = new JID(); + private boolean contactHas85Caps_; + private boolean contactHasSentActive_; + private boolean userIsTyping_; + private boolean contactIsOnline_; + private SignalConnection onCapsChangedConnection; + + public ChatStateNotifier(StanzaChannel stanzaChannel, JID contact, EntityCapsProvider entityCapsManager) { + this.stanzaChannel_ = stanzaChannel; + this.contact_ = contact; + this.entityCapsManager_ = entityCapsManager; + setContact(contact); + onCapsChangedConnection = entityCapsManager_.onCapsChanged.connect(new Slot1() { + + public void call(JID j1) { + handleCapsChanged(j1); + } + }); + } + + private boolean contactShouldReceiveStates() { + /* So, yes, the XEP says to look at caps, but it also says that once you've + heard from the contact, the active state overrides this. + *HOWEVER* it says that the MUST NOT send csn if you haven't received + active is OPTIONAL behaviour for if you haven't got caps.*/ + return contactIsOnline_ && (contactHasSentActive_ || contactHas85Caps_); + } + + private void changeState(ChatState.ChatStateType state) { + Message message = new Message(); + message.setTo(contact_); + message.addPayload(new ChatState(state)); + stanzaChannel_.sendMessage(message); + } + + private void handleCapsChanged(JID jid) { + if (jid.equals(contact_)) { + DiscoInfo caps = entityCapsManager_.getCaps(contact_); + boolean hasCSN = (caps != null) && (caps.hasFeature(DiscoInfo.ChatStatesFeature)); + contactHas85Caps_ = hasCSN; + } + } + + public void setContact(JID contact) { + contactHasSentActive_ = false; + userIsTyping_ = false; + contactIsOnline_ = false; + contact_ = contact; + handleCapsChanged(contact_); + } + + public void addChatStateRequest(Message message) { + if (contactShouldReceiveStates()) { + message.addPayload(new ChatState(ChatState.ChatStateType.Active)); + } + } + + public void setUserIsTyping() { + boolean should = contactShouldReceiveStates(); + if (should && !userIsTyping_) { + userIsTyping_ = true; + changeState(ChatState.ChatStateType.Composing); + } + } + + public void userSentMessage() { + userIsTyping_ = false; + } + + public void userCancelledNewMessage() { + if (userIsTyping_) { + userIsTyping_ = false; + changeState(ChatState.ChatStateType.Active); + } + } + + public void receivedMessageFromContact(boolean hasActiveElement) { + contactHasSentActive_ = hasActiveElement; + } + + public void setContactIsOnline(boolean online) { + contactIsOnline_ = online; + } +} diff --git a/src/com/isode/stroke/disco/DummyEntityCapsProvider.java b/src/com/isode/stroke/disco/DummyEntityCapsProvider.java new file mode 100644 index 0000000..708de7c --- /dev/null +++ b/src/com/isode/stroke/disco/DummyEntityCapsProvider.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2010 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ +/* + * Copyright (c) 2015 Tarun Gupta. + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +package com.isode.stroke.disco; + +import com.isode.stroke.disco.EntityCapsProvider; +import com.isode.stroke.jid.JID; +import com.isode.stroke.elements.DiscoInfo; +import java.util.Map; +import java.util.HashMap; + +public class DummyEntityCapsProvider extends EntityCapsProvider { + + public Map caps = new HashMap(); + + public DummyEntityCapsProvider() { + + } + + public DiscoInfo getCaps(JID jid) { + if(caps.containsKey(jid)) { + return caps.get(jid); + } + return null; + } +} \ No newline at end of file diff --git a/test/com/isode/stroke/chat/ChatStateNotifierTest.java b/test/com/isode/stroke/chat/ChatStateNotifierTest.java new file mode 100644 index 0000000..3d04c77 --- /dev/null +++ b/test/com/isode/stroke/chat/ChatStateNotifierTest.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2010 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ +/* + * Copyright (c) 2015 Tarun Gupta. + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +package com.isode.stroke.chat; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import org.junit.Test; +import org.junit.Before; +import com.isode.stroke.elements.ChatState; +import com.isode.stroke.elements.DiscoInfo; +import com.isode.stroke.elements.Stanza; +import com.isode.stroke.elements.Message; +import com.isode.stroke.jid.JID; +import com.isode.stroke.chat.ChatStateNotifier; +import com.isode.stroke.client.DummyStanzaChannel; +import com.isode.stroke.disco.DummyEntityCapsProvider; + +public class ChatStateNotifierTest { + + private DummyStanzaChannel stanzaChannel; + private DummyEntityCapsProvider entityCapsProvider; + private ChatStateNotifier notifier_; + + public ChatStateNotifierTest() { + + } + + @Before + public void setUp() { + stanzaChannel = new DummyStanzaChannel(); + stanzaChannel.setAvailable(true); + entityCapsProvider = new DummyEntityCapsProvider(); + notifier_ = new ChatStateNotifier(stanzaChannel, new JID("foo@bar.com/baz"), entityCapsProvider); + notifier_.setContactIsOnline(true); + } + + private void setContactHas85Caps() { + DiscoInfo caps = new DiscoInfo(); + caps.addFeature(DiscoInfo.ChatStatesFeature); + entityCapsProvider.caps.put(new JID("foo@bar.com/baz"), caps); + entityCapsProvider.onCapsChanged.emit(new JID("foo@bar.com/baz")); + } + + private int getComposingCount() { + int result = 0; + for(Stanza stanza : stanzaChannel.sentStanzas) { + if (stanza.getPayload(new ChatState()) != null && stanza.getPayload(new ChatState()).getChatState() == ChatState.ChatStateType.Composing) { + result++; + } + } + return result; + } + + private int getActiveCount() { + int result = 0; + for(Stanza stanza : stanzaChannel.sentStanzas) { + if (stanza.getPayload(new ChatState()) != null && stanza.getPayload(new ChatState()).getChatState() == ChatState.ChatStateType.Active) { + result++; + } + } + return result; + } + + @Test + public void testStartTypingReply_CapsNotIncluded() { + notifier_.setUserIsTyping(); + assertEquals(0, getComposingCount()); + } + + + @Test + public void testSendTwoMessages() { + setContactHas85Caps(); + notifier_.setUserIsTyping(); + notifier_.userSentMessage(); + notifier_.setUserIsTyping(); + notifier_.userSentMessage(); + assertEquals(2, getComposingCount()); + } + + + @Test + public void testCancelledNewMessage() { + setContactHas85Caps(); + notifier_.setUserIsTyping(); + notifier_.userCancelledNewMessage(); + assertEquals(1, getComposingCount()); + assertEquals(1, getActiveCount()); + assertEquals(ChatState.ChatStateType.Active, stanzaChannel.sentStanzas.get(stanzaChannel.sentStanzas.size()-1).getPayload(new ChatState()).getChatState()); + } + + @Test + public void testContactShouldReceiveStates_CapsOnly() { + setContactHas85Caps(); + Message message = new Message(); + notifier_.addChatStateRequest(message); + assertNotNull(message.getPayload(new ChatState())); + assertEquals(ChatState.ChatStateType.Active, message.getPayload(new ChatState()).getChatState()); + } + + @Test + public void testContactShouldReceiveStates_CapsNorActive() { + Message message = new Message(); + notifier_.addChatStateRequest(message); + assertNull(message.getPayload(new ChatState())); + } + + @Test + public void testContactShouldReceiveStates_ActiveOverrideOn() { + notifier_.receivedMessageFromContact(true); + Message message = new Message(); + notifier_.addChatStateRequest(message); + assertNotNull(message.getPayload(new ChatState())); + assertEquals(ChatState.ChatStateType.Active, message.getPayload(new ChatState()).getChatState()); + } + + @Test + public void testContactShouldReceiveStates_ActiveOverrideOff() { + setContactHas85Caps(); + notifier_.receivedMessageFromContact(false); + /* I originally read the MUST NOT send after receiving without Active and + * thought this should check for false, but I later found it was OPTIONAL + * (MAY) behaviour only for if you didn't receive caps. + */ + Message message = new Message(); + notifier_.addChatStateRequest(message); + assertNotNull(message.getPayload(new ChatState())); + assertEquals(ChatState.ChatStateType.Active, message.getPayload(new ChatState()).getChatState()); + } + + + @Test + public void testStartTypingReply_CapsIncluded() { + setContactHas85Caps(); + notifier_.setUserIsTyping(); + assertEquals(1, getComposingCount()); + } + + @Test + public void testContinueTypingReply_CapsIncluded() { + setContactHas85Caps(); + notifier_.setUserIsTyping(); + notifier_.setUserIsTyping(); + notifier_.setUserIsTyping(); + assertEquals(1, getComposingCount()); + notifier_.userSentMessage(); + notifier_.setUserIsTyping(); + assertEquals(2, getComposingCount()); + } + + @Test + public void testTypeReplies_WentOffline() { + setContactHas85Caps(); + notifier_.setUserIsTyping(); + assertEquals(1, getComposingCount()); + notifier_.setContactIsOnline(false); + notifier_.userSentMessage(); + notifier_.setUserIsTyping(); + assertEquals(1, getComposingCount()); + } +} \ No newline at end of file -- cgit v0.10.2-6-g49f6