From 1bc3e10cffe3e30817518adefc0a493adef00fb0 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Mon, 19 Jan 2015 16:23:50 +0100 Subject: [PATCH] Improve Message Delivery Receipt (XEP-184) API add a new AutoReceiptMode enum that specifies how delivery receipt requests are handled. Default is to send receipts if the requstor is subscribed to the user's presence. Also make sure that messages contain an id if a receipt request is added to it. --- .../smack/filter/MessageTypeFilter.java | 2 + .../smackx/receipts/DeliveryReceipt.java | 13 +- .../receipts/DeliveryReceiptManager.java | 138 ++++++++++++++---- .../receipts/DeliveryReceiptRequest.java | 4 + .../extensions.xml | 1 + .../smackx/receipts/DeliveryReceiptTest.java | 5 +- 6 files changed, 130 insertions(+), 33 deletions(-) diff --git a/smack-core/src/main/java/org/jivesoftware/smack/filter/MessageTypeFilter.java b/smack-core/src/main/java/org/jivesoftware/smack/filter/MessageTypeFilter.java index 88342de34..c7eaa5275 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/filter/MessageTypeFilter.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/filter/MessageTypeFilter.java @@ -35,6 +35,8 @@ public class MessageTypeFilter extends FlexiblePacketTypeFilter { public static final PacketFilter HEADLINE = new MessageTypeFilter(Type.headline); public static final PacketFilter ERROR = new MessageTypeFilter(Type.error); public static final PacketFilter NORMAL_OR_CHAT = new OrFilter(NORMAL, CHAT); + public static final PacketFilter NORMAL_OR_CHAT_OR_HEADLINE = new OrFilter(NORMAL_OR_CHAT, + HEADLINE); private final Message.Type type; diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/receipts/DeliveryReceipt.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/receipts/DeliveryReceipt.java index 3fa48fdec..b278257eb 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/receipts/DeliveryReceipt.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/receipts/DeliveryReceipt.java @@ -22,6 +22,7 @@ import java.util.Map; import org.jivesoftware.smack.packet.Packet; import org.jivesoftware.smack.packet.PacketExtension; import org.jivesoftware.smack.provider.EmbeddedExtensionProvider; +import org.jivesoftware.smack.util.XmlStringBuilder; /** * Represents a message delivery receipt entry as specified by @@ -34,7 +35,10 @@ public class DeliveryReceipt implements PacketExtension public static final String NAMESPACE = "urn:xmpp:receipts"; public static final String ELEMENT = "received"; - private String id; /// original ID of the delivered message + /** + * original ID of the delivered message + */ + private final String id; public DeliveryReceipt(String id) { @@ -59,9 +63,12 @@ public class DeliveryReceipt implements PacketExtension } @Override - public String toXML() + public XmlStringBuilder toXML() { - return ""; + XmlStringBuilder xml = new XmlStringBuilder(this); + xml.attribute("id", id); + xml.closeEmptyElement(); + return xml; } /** diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/receipts/DeliveryReceiptManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/receipts/DeliveryReceiptManager.java index 6693afff1..934827e6a 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/receipts/DeliveryReceiptManager.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/receipts/DeliveryReceiptManager.java @@ -1,6 +1,6 @@ /** * - * Copyright 2013-2014 Georg Lukas + * Copyright 2013-2014 Georg Lukas, 2015 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,6 +30,7 @@ import org.jivesoftware.smack.PacketListener; import org.jivesoftware.smack.XMPPConnectionRegistry; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.filter.AndFilter; +import org.jivesoftware.smack.filter.MessageTypeFilter; import org.jivesoftware.smack.filter.PacketExtensionFilter; import org.jivesoftware.smack.filter.PacketFilter; import org.jivesoftware.smack.filter.PacketTypeFilter; @@ -42,6 +43,24 @@ import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; * the manager for {@link DeliveryReceipt} support, enabling and disabling of * automatic DeliveryReceipt transmission. * + *

+ * You can send delivery receipt requests and listen for incoming delivery receipts as shown in this example: + *

+ *
+ * deliveryReceiptManager.addReceiptReceivedListener(new ReceiptReceivedListener() {
+ *   void onReceiptReceived(String fromJid, String toJid, String receiptId, Packet receipt) {
+ *     // If the receiving entity does not support delivery receipts,
+ *     // then the receipt received listener may not get invoked.
+ *   }
+ * });
+ * Message message = …
+ * DeliveryReceiptRequest.addTo(message);
+ * connection.sendPacket(message);
+ * 
+ * + * DeliveryReceiptManager can be configured to automatically add delivery receipt requests to every + * message with {@link #autoAddDeliveryReceiptRequests()}. + * * @author Georg Lukas * @see XEP-0184: Message Delivery Receipts */ @@ -62,7 +81,43 @@ public class DeliveryReceiptManager extends Manager { }); } - private boolean auto_receipts_enabled = false; + /** + * Specifies when incoming message delivery receipt requests should be automatically + * acknowledged with an receipt. + */ + public enum AutoReceiptMode { + + /** + * Never send deliver receipts + */ + disabled, + + /** + * Only send delivery receipts if the requester is subscribed to our presence. + */ + ifIsSubscribed, + + /** + * Always send delivery receipts. Warning: this may causes presence leaks. See XEP-0184: Message Delivery + * Receipts § 8. Security Considerations + */ + always, + } + + private static AutoReceiptMode defaultAutoReceiptMode = AutoReceiptMode.ifIsSubscribed; + + /** + * Set the default automatic receipt mode for new connections. + * + * @param autoReceiptMode the default automatic receipt mode. + */ + public static void setDefaultAutoReceiptMode(AutoReceiptMode autoReceiptMode) { + defaultAutoReceiptMode = autoReceiptMode; + } + + private AutoReceiptMode autoReceiptMode = defaultAutoReceiptMode; + private final Set receiptReceivedListeners = new CopyOnWriteArraySet(); private DeliveryReceiptManager(XMPPConnection connection) { @@ -86,13 +141,23 @@ public class DeliveryReceiptManager extends Manager { connection.addAsyncPacketListener(new PacketListener() { @Override public void processPacket(Packet packet) throws NotConnectedException { - // if enabled, automatically send a receipt - if (!auto_receipts_enabled) { + final String from = packet.getFrom(); + final XMPPConnection connection = connection(); + switch (autoReceiptMode) { + case disabled: return; + case ifIsSubscribed: + if (!connection.getRoster().isSubscribedToMyPresence(from)) { + return; + } + break; + case always: + break; } - Message ack = new Message(packet.getFrom(), Message.Type.normal); + + Message ack = new Message(from, Message.Type.normal); ack.addExtension(new DeliveryReceipt(packet.getPacketID())); - connection().sendPacket(ack); + connection.sendPacket(ack); } }, MESSAGES_WITH_DEVLIERY_RECEIPT_REQUEST); } @@ -130,34 +195,22 @@ public class DeliveryReceiptManager extends Manager { /** * Configure whether the {@link DeliveryReceiptManager} should automatically - * reply to incoming {@link DeliveryReceipt}s. By default, this feature is off. + * reply to incoming {@link DeliveryReceipt}s. * - * @param new_state whether automatic transmission of - * DeliveryReceipts should be enabled or disabled + * @param autoReceiptMode the new auto receipt mode. + * @see AutoReceiptMode */ - public void setAutoReceiptsEnabled(boolean new_state) { - auto_receipts_enabled = new_state; + public void setAutoReceiptMode(AutoReceiptMode autoReceiptMode) { + this.autoReceiptMode = autoReceiptMode; } /** - * Helper method to enable automatic DeliveryReceipt transmission. + * Get the currently active auto receipt mode. + * + * @return the currently active auto receipt mode. */ - public void enableAutoReceipts() { - setAutoReceiptsEnabled(true); - } - - /** - * Helper method to disable automatic DeliveryReceipt transmission. - */ - public void disableAutoReceipts() { - setAutoReceiptsEnabled(false); - } - - /** - * Check if AutoReceipts are enabled on this connection. - */ - public boolean getAutoReceiptsEnabled() { - return this.auto_receipts_enabled; + public AutoReceiptMode getAutoReceiptMode() { + return autoReceiptMode; } /** @@ -178,6 +231,35 @@ public class DeliveryReceiptManager extends Manager { receiptReceivedListeners.remove(listener); } + private static final PacketListener AUTO_ADD_DELIVERY_RECEIPT_REQUESTS_LISTENER = new PacketListener() { + @Override + public void processPacket(Packet packet) throws NotConnectedException { + Message message = (Message) packet; + DeliveryReceiptRequest.addTo(message); + } + }; + + /** + * Enables automatic requests of delivery receipts for outgoing messages of type 'normal', 'chat' or 'headline. + * + * @since 4.1 + * @see #dontAutoAddDeliveryReceiptRequests() + */ + public void autoAddDeliveryReceiptRequests() { + connection().addPacketSendingListener(AUTO_ADD_DELIVERY_RECEIPT_REQUESTS_LISTENER, + MessageTypeFilter.NORMAL_OR_CHAT_OR_HEADLINE); + } + + /** + * Disables automatically requests of delivery receipts for outgoing messages. + * + * @since 4.1 + * @see #autoAddDeliveryReceiptRequests() + */ + public void dontAutoAddDeliveryReceiptRequests() { + connection().removePacketSendingListener(AUTO_ADD_DELIVERY_RECEIPT_REQUESTS_LISTENER); + } + /** * Test if a message requires a delivery receipt. * diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/receipts/DeliveryReceiptRequest.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/receipts/DeliveryReceiptRequest.java index 612be4575..a3a29ad0d 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/receipts/DeliveryReceiptRequest.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/receipts/DeliveryReceiptRequest.java @@ -21,6 +21,7 @@ import java.io.IOException; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Packet; import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.packet.id.StanzaIdUtil; import org.jivesoftware.smack.provider.PacketExtensionProvider; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -82,6 +83,9 @@ public class DeliveryReceiptRequest implements PacketExtension * @return the Message ID which will be used as receipt ID */ public static String addTo(Message message) { + if (message.getPacketID() == null) { + message.setPacketID(StanzaIdUtil.newStanzaId()); + } message.addExtension(new DeliveryReceiptRequest()); return message.getPacketID(); } diff --git a/smack-extensions/src/main/resources/org.jivesoftware.smack.extensions/extensions.xml b/smack-extensions/src/main/resources/org.jivesoftware.smack.extensions/extensions.xml index 6974562fb..b6e64c901 100644 --- a/smack-extensions/src/main/resources/org.jivesoftware.smack.extensions/extensions.xml +++ b/smack-extensions/src/main/resources/org.jivesoftware.smack.extensions/extensions.xml @@ -15,5 +15,6 @@ org.jivesoftware.smackx.xdata.XDataManager org.jivesoftware.smackx.xdatalayout.XDataLayoutManager org.jivesoftware.smackx.xdatavalidation.XDataValidationManager + org.jivesoftware.smackx.receipts.DeliveryReceiptManager diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/receipts/DeliveryReceiptTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/receipts/DeliveryReceiptTest.java index 10cc98852..3eb1a3ef6 100644 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/receipts/DeliveryReceiptTest.java +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/receipts/DeliveryReceiptTest.java @@ -29,6 +29,7 @@ import org.jivesoftware.smack.packet.Packet; import org.jivesoftware.smack.test.util.WaitForPacketListener; import org.jivesoftware.smack.util.PacketParserUtils; import org.jivesoftware.smackx.InitExtensions; +import org.jivesoftware.smackx.receipts.DeliveryReceiptManager.AutoReceiptMode; import org.junit.Test; import org.xmlpull.v1.XmlPullParser; @@ -101,8 +102,8 @@ public class DeliveryReceiptTest extends InitExtensions { c.connect(); DeliveryReceiptManager drm = DeliveryReceiptManager.getInstanceFor(c); - drm.enableAutoReceipts(); - assertTrue(drm.getAutoReceiptsEnabled()); + drm.setAutoReceiptMode(AutoReceiptMode.always); + assertEquals(AutoReceiptMode.always, drm.getAutoReceiptMode()); // test auto-receipts Message m = new Message("julia@capulet.com", Message.Type.normal);