received and displayed messages (xep 333) are now marked properly

iNPUTmice created

Change summary

res/values/arrays.xml                                          | 12 
res/values/strings.xml                                         |  4 
res/xml/preferences.xml                                        | 12 
src/eu/siacs/conversations/entities/Conversation.java          | 65 ++-
src/eu/siacs/conversations/entities/Message.java               |  2 
src/eu/siacs/conversations/parser/MessageParser.java           | 16 
src/eu/siacs/conversations/services/XmppConnectionService.java | 66 ++-
src/eu/siacs/conversations/ui/ConversationActivity.java        |  2 
src/eu/siacs/conversations/ui/ConversationFragment.java        |  2 
src/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java     |  2 
10 files changed, 116 insertions(+), 67 deletions(-)

Detailed changes

res/values/arrays.xml 🔗

@@ -19,16 +19,4 @@
         <item>524288</item>
         <item>1048576</item>
     </string-array>
-    <string-array name="message_acks">
-        <item>never</item>
-        <item>when received</item>
-        <item>when read</item>
-        <item>when received and when read</item>
-    </string-array>
-    <string-array name="message_acks_values">
-		<item>0</item>
-		<item>1</item>
-		<item>2</item>
-		<item>3</item>        
-    </string-array>
 </resources>

res/values/strings.xml 🔗

@@ -145,8 +145,8 @@
     <string name="pref_advanced_options">Advanced Options</string>
     <string name="pref_never_send_crash">Never send crash reports</string>
     <string name="pref_never_send_crash_summary">By sending in stack traces you are helping the ongoing development of Conversations</string>
-    <string name="pref_auto_acknowledge_messages">Acknowledge Messages</string>
-    <string name="pref_auto_acknowledge_messages_summary">Allows your contact to know whether or not you have received or read a specific message</string>
+    <string name="pref_confirm_messages">Confirm Messages</string>
+    <string name="pref_confirm_messages_summary">Let your contact know when you have received and read a message</string>
     <string name="openpgp_error">OpenKeychain reporeted an error</string>
     <string name="error_decrypting_file">I/O Error decrypting file</string>
     <string name="error_copying_image_file">Error copying image file.</string>

res/xml/preferences.xml 🔗

@@ -22,13 +22,11 @@
             android:entries="@array/filesizes"
             android:entryValues="@array/filesizes_values"
             android:defaultValue="524288"/>
-        <ListPreference 
-            android:key="auto_acknowledge_messages"
-            android:title="@string/pref_auto_acknowledge_messages"
-            android:summary="@string/pref_auto_acknowledge_messages_summary"
-            android:entries="@array/message_acks"
-            android:entryValues="@array/message_acks_values"
-            android:defaultValue="2"
+        <CheckBoxPreference 
+            android:key="confirm_messages"
+            android:title="@string/pref_confirm_messages"
+            android:summary="@string/pref_confirm_messages_summary"
+            android:defaultValue="true"
             />
     </PreferenceCategory>
     <PreferenceCategory 

src/eu/siacs/conversations/entities/Conversation.java 🔗

@@ -4,6 +4,8 @@ import java.security.interfaces.DSAPublicKey;
 import java.util.ArrayList;
 import java.util.List;
 
+import eu.siacs.conversations.services.XmppConnectionService;
+
 import net.java.otr4j.OtrException;
 import net.java.otr4j.crypto.OtrCryptoEngineImpl;
 import net.java.otr4j.crypto.OtrCryptoException;
@@ -44,7 +46,7 @@ public class Conversation extends AbstractEntity {
 	private int status;
 	private long created;
 	private int mode;
-	
+
 	private String nextPresence;
 
 	private transient List<Message> messages = null;
@@ -59,6 +61,8 @@ public class Conversation extends AbstractEntity {
 
 	private transient MucOptions mucOptions = null;
 
+	private transient String latestMarkableMessageId;
+
 	public Conversation(String name, Account account, String contactJid,
 			int mode) {
 		this(java.util.UUID.randomUUID().toString(), name, null, account
@@ -100,15 +104,26 @@ public class Conversation extends AbstractEntity {
 	}
 
 	public void markRead() {
-		if (this.messages == null)
+		if (this.messages == null) {
 			return;
+		}
 		for (int i = this.messages.size() - 1; i >= 0; --i) {
-			if (messages.get(i).isRead())
-				return;
+			if (messages.get(i).isRead()) {
+				break;
+			}
 			this.messages.get(i).markRead();
 		}
 	}
 
+	public void markRead(XmppConnectionService service) {
+		markRead();
+		if (service.confirmMessages() && this.latestMarkableMessageId != null) {
+			service.sendConfirmMessage(getAccount(), getContactJid(),
+					this.latestMarkableMessageId);
+			this.latestMarkableMessageId = null;
+		}
+	}
+
 	public Message getLatestMessage() {
 		if ((this.messages == null) || (this.messages.size() == 0)) {
 			Message message = new Message(this, "", Message.ENCRYPTION_NONE);
@@ -126,7 +141,8 @@ public class Conversation extends AbstractEntity {
 	}
 
 	public String getName(boolean useSubject) {
-		if ((getMode() == MODE_MULTI) && (getMucOptions().getSubject() != null) && useSubject) {
+		if ((getMode() == MODE_MULTI) && (getMucOptions().getSubject() != null)
+				&& useSubject) {
 			return getMucOptions().getSubject();
 		} else {
 			return this.getContact().getDisplayName();
@@ -208,14 +224,15 @@ public class Conversation extends AbstractEntity {
 		this.mode = mode;
 	}
 
-	public SessionImpl startOtrSession(Context context, String presence, boolean sendStart) {
+	public SessionImpl startOtrSession(Context context, String presence,
+			boolean sendStart) {
 		if (this.otrSession != null) {
 			return this.otrSession;
 		} else {
 			SessionID sessionId = new SessionID(this.getContactJid(), presence,
 					"xmpp");
-			this.otrSession = new SessionImpl(sessionId, getAccount().getOtrEngine(
-					context));
+			this.otrSession = new SessionImpl(sessionId, getAccount()
+					.getOtrEngine(context));
 			try {
 				if (sendStart) {
 					this.otrSession.startSession();
@@ -226,13 +243,13 @@ public class Conversation extends AbstractEntity {
 				return null;
 			}
 		}
-		
+
 	}
 
 	public SessionImpl getOtrSession() {
 		return this.otrSession;
 	}
-	
+
 	public void resetOtrSession() {
 		this.otrSession = null;
 	}
@@ -240,7 +257,8 @@ public class Conversation extends AbstractEntity {
 	public void endOtrIfNeeded() {
 		if (this.otrSession != null) {
 			if (this.otrSession.getSessionStatus() == SessionStatus.ENCRYPTED) {
-				Log.d("xmppService","ending otr session with "+getContactJid());
+				Log.d("xmppService", "ending otr session with "
+						+ getContactJid());
 				try {
 					this.otrSession.endSession();
 					this.resetOtrSession();
@@ -289,44 +307,51 @@ public class Conversation extends AbstractEntity {
 	public void setContactJid(String jid) {
 		this.contactJid = jid;
 	}
-	
+
 	public void setNextPresence(String presence) {
 		this.nextPresence = presence;
 	}
-	
+
 	public String getNextPresence() {
 		return this.nextPresence;
 	}
-	
+
 	public int getLatestEncryption() {
 		int latestEncryption = this.getLatestMessage().getEncryption();
-		if ((latestEncryption == Message.ENCRYPTION_DECRYPTED) || (latestEncryption == Message.ENCRYPTION_DECRYPTION_FAILED)) {
+		if ((latestEncryption == Message.ENCRYPTION_DECRYPTED)
+				|| (latestEncryption == Message.ENCRYPTION_DECRYPTION_FAILED)) {
 			return Message.ENCRYPTION_PGP;
 		} else {
 			return latestEncryption;
 		}
 	}
-	
+
 	public int getNextEncryption() {
 		if (this.nextMessageEncryption == -1) {
 			return this.getLatestEncryption();
 		}
 		return this.nextMessageEncryption;
 	}
-	
+
 	public void setNextEncryption(int encryption) {
 		this.nextMessageEncryption = encryption;
 	}
-	
+
 	public String getNextMessage() {
-		if (this.nextMessage==null) {
+		if (this.nextMessage == null) {
 			return "";
 		} else {
 			return this.nextMessage;
 		}
 	}
-	
+
 	public void setNextMessage(String message) {
 		this.nextMessage = message;
 	}
+
+	public void setLatestMarkableMessageId(String id) {
+		if (id != null) {
+			this.latestMarkableMessageId = id;
+		}
+	}
 }

src/eu/siacs/conversations/entities/Message.java 🔗

@@ -20,6 +20,8 @@ public class Message extends AbstractEntity {
 	public static final int STATUS_SEND_FAILED = 3;
 	public static final int STATUS_SEND_REJECTED = 4;
 	public static final int STATUS_OFFERED = 6;
+	public static final int STATUS_SEND_RECEIVED = 7;
+	public static final int STATUS_SEND_DISPLAYED = 8;
 
 	public static final int ENCRYPTION_NONE = 0;
 	public static final int ENCRYPTION_PGP = 1;

src/eu/siacs/conversations/parser/MessageParser.java 🔗

@@ -25,6 +25,7 @@ public class MessageParser {
 		String[] fromParts = packet.getFrom().split("/");
 		Conversation conversation = mXmppConnectionService
 				.findOrCreateConversation(account, fromParts[0], false);
+		conversation.setLatestMarkableMessageId(getMarkableMessageId(packet));
 		String pgpBody = getPgpBody(packet);
 		if (pgpBody != null) {
 			return new Message(conversation, packet.getFrom(), pgpBody,
@@ -104,6 +105,7 @@ public class MessageParser {
 			if ((body == null) || (body.isEmpty())) {
 				return null;
 			}
+			conversation.setLatestMarkableMessageId(getMarkableMessageId(packet));
 			return new Message(conversation, packet.getFrom(), body,
 					Message.ENCRYPTION_OTR, Message.STATUS_RECIEVED);
 		} catch (Exception e) {
@@ -138,6 +140,7 @@ public class MessageParser {
 			status = Message.STATUS_RECIEVED;
 		}
 		String pgpBody = getPgpBody(packet);
+		conversation.setLatestMarkableMessageId(getMarkableMessageId(packet));
 		if (pgpBody == null) {
 			return new Message(conversation, counterPart, packet.getBody(),
 					Message.ENCRYPTION_NONE, status);
@@ -174,6 +177,7 @@ public class MessageParser {
 		String[] parts = fullJid.split("/");
 		Conversation conversation = mXmppConnectionService
 				.findOrCreateConversation(account, parts[0], false);
+		conversation.setLatestMarkableMessageId(getMarkableMessageId(packet));
 		String pgpBody = getPgpBody(message);
 		if (pgpBody != null) {
 			return new Message(conversation, fullJid, pgpBody,
@@ -191,12 +195,20 @@ public class MessageParser {
 				packet.getId(), Message.STATUS_SEND_FAILED);
 	}
 
-	private String getPgpBody(Element packet) {
-		Element child = packet.findChild("x", "jabber:x:encrypted");
+	private String getPgpBody(Element message) {
+		Element child = message.findChild("x", "jabber:x:encrypted");
 		if (child == null) {
 			return null;
 		} else {
 			return child.getContent();
 		}
 	}
+	
+	private String getMarkableMessageId(Element message) {
+		if (message.hasChild("markable", "urn:xmpp:chat-markers:0")) {
+			return message.getAttribute("id");
+		} else {
+			return null;
+		}
+	}
 }

src/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -139,7 +139,6 @@ public class XmppConnectionService extends Service {
 					"notification_grace_period_after_carbon_received", true)) {
 				notify = (SystemClock.elapsedRealtime() - lastCarbonMessageReceived) > CARBON_GRACE_PERIOD;
 			}
-			
 
 			if ((packet.getType() == MessagePacket.TYPE_CHAT)) {
 				if ((packet.getBody() != null)
@@ -149,8 +148,7 @@ public class XmppConnectionService extends Service {
 						message.markUnread();
 					}
 				} else if (packet.hasChild("body")) {
-					message = mMessageParser
-							.parseChat(packet, account);
+					message = mMessageParser.parseChat(packet, account);
 					message.markUnread();
 				} else if (packet.hasChild("received")
 						|| (packet.hasChild("sent"))) {
@@ -182,7 +180,16 @@ public class XmppConnectionService extends Service {
 				mMessageParser.parseError(packet, account);
 				return;
 			} else if (packet.getType() == MessagePacket.TYPE_NORMAL) {
-				if (packet.hasChild("x")) {
+				if (packet.hasChild("displayed","urn:xmpp:chat-markers:0")) {
+					String id = packet.findChild("displayed","urn:xmpp:chat-markers:0").getAttribute("id");
+					String[] fromParts = packet.getFrom().split("/");
+					markMessage(account,fromParts[0], id, Message.STATUS_SEND_DISPLAYED);
+					Log.d(LOGTAG,"message was displayed by contact");
+				} else if (packet.hasChild("received","urn:xmpp:chat-markers:0")) {
+					String id = packet.findChild("received","urn:xmpp:chat-markers:0").getAttribute("id");
+					String[] fromParts = packet.getFrom().split("/");
+					markMessage(account,fromParts[0], id, Message.STATUS_SEND_RECEIVED);
+				} else if (packet.hasChild("x")) {
 					Element x = packet.findChild("x");
 					if (x.hasChild("invite")) {
 						findOrCreateConversation(account, packet.getFrom(),
@@ -195,7 +202,7 @@ public class XmppConnectionService extends Service {
 					}
 
 				} else {
-					// Log.d(LOGTAG, "unparsed message " + packet.toString());
+					//Log.d(LOGTAG, "unparsed message " + packet.toString());
 				}
 			}
 			if ((message == null) || (message.getBody() == null)) {
@@ -213,9 +220,9 @@ public class XmppConnectionService extends Service {
 					Log.d(LOGTAG, "error trying to parse date" + e.getMessage());
 				}
 			}
-			if ((confirmReception()) && ((packet.getId() != null))) {
+			if ((confirmMessages()) && ((packet.getId() != null))) {
 				MessagePacket receivedPacket = new MessagePacket();
-				receivedPacket.setType(MessagePacket.TYPE_UNKNOWN);
+				receivedPacket.setType(MessagePacket.TYPE_NORMAL);
 				receivedPacket.setTo(message.getCounterpart());
 				receivedPacket.setFrom(account.getFullJid());
 				if (packet.hasChild("markable", "urn:xmpp:chat-markers:0")) {
@@ -416,8 +423,9 @@ public class XmppConnectionService extends Service {
 						"urn:xmpp:jingle:transports:s5b:1");
 				query.addChild("feature").setAttribute("var",
 						"urn:xmpp:jingle:transports:ibb:1");
-				if (confirmReception()) {
-					query.addChild("feature").setAttribute("var", "urn:xmpp:receipts");
+				if (confirmMessages()) {
+					query.addChild("feature").setAttribute("var",
+							"urn:xmpp:receipts");
 				}
 				account.getXmppConnection().sendIqPacket(iqResponse, null);
 			} else {
@@ -968,8 +976,15 @@ public class XmppConnectionService extends Service {
 		Collections.sort(this.conversations, new Comparator<Conversation>() {
 			@Override
 			public int compare(Conversation lhs, Conversation rhs) {
-				return (int) (rhs.getLatestMessage().getTimeSent() - lhs
-						.getLatestMessage().getTimeSent());
+				Message left = lhs.getLatestMessage();
+				Message right = rhs.getLatestMessage();
+				if (left.getTimeSent() > right.getTimeSent()) {
+					return -1;
+				} else if (left.getTimeSent() < right.getTimeSent()) {
+					return 1;
+				} else {
+					return 0;
+				}
 			}
 		});
 		return this.conversations;
@@ -1442,17 +1457,9 @@ public class XmppConnectionService extends Service {
 		return PreferenceManager
 				.getDefaultSharedPreferences(getApplicationContext());
 	}
-	
-	private boolean confirmReception() {
-		String autoAcks = getPreferences().getString(
-				"auto_acknowledge_messages", "2");
-		int autoAcksValue;
-		try {
-			autoAcksValue = Integer.parseInt(autoAcks);
-		} catch (NumberFormatException e) {
-			autoAcksValue = 0;
-		}
-		return autoAcksValue == 1 || autoAcksValue == 3;
+
+	public boolean confirmMessages() {
+		return getPreferences().getBoolean("confirm_messages", true);
 	}
 
 	public void updateUi(Conversation conversation, boolean notify) {
@@ -1472,4 +1479,19 @@ public class XmppConnectionService extends Service {
 		}
 		return null;
 	}
+
+	public void markRead(Conversation conversation) {
+		conversation.markRead(this);
+	}
+
+	public void sendConfirmMessage(Account account, String to, String id) {
+		MessagePacket receivedPacket = new MessagePacket();
+		receivedPacket.setType(MessagePacket.TYPE_NORMAL);
+		receivedPacket.setTo(to);
+		receivedPacket.setFrom(account.getFullJid());
+		Element received = receivedPacket.addChild("displayed",
+				"urn:xmpp:chat-markers:0");
+		received.setAttribute("id", id);
+		account.getXmppConnection().sendMessagePacket(receivedPacket);
+	}
 }

src/eu/siacs/conversations/ui/ConversationActivity.java 🔗

@@ -279,7 +279,7 @@ public class ConversationActivity extends XmppActivity {
 							getSelectedConversation().getName(useSubject));
 					invalidateOptionsMenu();
 					if (!getSelectedConversation().isRead()) {
-						getSelectedConversation().markRead();
+						xmppConnectionService.markRead(getSelectedConversation());
 						UIHelper.updateNotification(getApplicationContext(),
 								getConversationList(), null, false);
 						listView.invalidateViews();

src/eu/siacs/conversations/ui/ConversationFragment.java 🔗

@@ -641,7 +641,7 @@ public class ConversationFragment extends Fragment {
 			if (size >= 1)
 				messagesView.setSelection(size - 1);
 			if (!activity.shouldPaneBeOpen()) {
-				conversation.markRead();
+				activity.xmppConnectionService.markRead(conversation);
 				// TODO update notifications
 				UIHelper.updateNotification(getActivity(),
 						activity.getConversationList(), null, false);