summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/com/isode/stroke/chat/ChatStateNotifier.java110
-rw-r--r--src/com/isode/stroke/disco/DummyEntityCapsProvider.java34
-rw-r--r--test/com/isode/stroke/chat/ChatStateNotifierTest.java173
3 files changed, 317 insertions, 0 deletions
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<JID>() {
+
+ 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<JID, DiscoInfo> caps = new HashMap<JID, DiscoInfo>();
+
+ 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