Detailed changes
  
  
    
    @@ -375,7 +375,7 @@
         <xmpp:SupportedXep>
             <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0377.html"/>
             <xmpp:status>complete</xmpp:status>
-            <xmpp:version>0.2</xmpp:version>
+            <xmpp:version>0.3.1</xmpp:version>
         </xmpp:SupportedXep>
     </implements>
     <implements>
  
  
  
    
    @@ -344,12 +344,18 @@ public class IqGenerator extends AbstractGenerator {
         return iq;
     }
 
-    public IqPacket generateSetBlockRequest(final Jid jid, boolean reportSpam) {
+    public IqPacket generateSetBlockRequest(final Jid jid, final boolean reportSpam, final String serverMsgId) {
         final IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
         final Element block = iq.addChild("block", Namespace.BLOCKING);
         final Element item = block.addChild("item").setAttribute("jid", jid);
         if (reportSpam) {
-            item.addChild("report", "urn:xmpp:reporting:0").addChild("spam");
+            final Element report = item.addChild("report", Namespace.REPORTING);
+            report.setAttribute("reason", Namespace.REPORTING_REASON_SPAM);
+            if (serverMsgId != null) {
+                final Element stanzaId = report.addChild("stanza-id", Namespace.STANZA_IDS);
+                stanzaId.setAttribute("by", jid);
+                stanzaId.setAttribute("id", serverMsgId);
+            }
         }
         Log.d(Config.LOGTAG, iq.toString());
         return iq;
  
  
  
    
    @@ -4756,10 +4756,10 @@ public class XmppConnectionService extends Service {
         mDatabaseWriterExecutor.execute(runnable);
     }
 
-    public boolean sendBlockRequest(final Blockable blockable, boolean reportSpam) {
+    public boolean sendBlockRequest(final Blockable blockable, final boolean reportSpam, final String serverMsgId) {
         if (blockable != null && blockable.getBlockedJid() != null) {
             final Jid jid = blockable.getBlockedJid();
-            this.sendIqPacket(blockable.getAccount(), getIqGenerator().generateSetBlockRequest(jid, reportSpam), (a, response) -> {
+            this.sendIqPacket(blockable.getAccount(), getIqGenerator().generateSetBlockRequest(jid, reportSpam, serverMsgId), (a, response) -> {
                 if (response.getType() == IqPacket.TYPE.RESULT) {
                     a.getBlocklist().add(jid);
                     updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED);
  
  
  
    
    @@ -14,13 +14,27 @@ import eu.siacs.conversations.entities.Conversation;
 import eu.siacs.conversations.ui.util.JidDialog;
 
 public final class BlockContactDialog {
+
 	public static void show(final XmppActivity xmppActivity, final Blockable blockable) {
+		show(xmppActivity, blockable, null);
+	}
+	public static void show(final XmppActivity xmppActivity, final Blockable blockable, final String serverMsgId) {
 		final AlertDialog.Builder builder = new AlertDialog.Builder(xmppActivity);
 		final boolean isBlocked = blockable.isBlocked();
 		builder.setNegativeButton(R.string.cancel, null);
 		DialogBlockContactBinding binding = DataBindingUtil.inflate(xmppActivity.getLayoutInflater(), R.layout.dialog_block_contact, null, false);
 		final boolean reporting = blockable.getAccount().getXmppConnection().getFeatures().spamReporting();
-		binding.reportSpam.setVisibility(!isBlocked && reporting ? View.VISIBLE : View.GONE);
+		if (reporting && !isBlocked) {
+			binding.reportSpam.setVisibility(View.VISIBLE);
+			if (serverMsgId != null) {
+				binding.reportSpam.setChecked(true);
+				binding.reportSpam.setEnabled(false);
+			} else {
+				binding.reportSpam.setEnabled(true);
+			}
+		} else {
+			binding.reportSpam.setVisibility(View.GONE);
+		}
 		builder.setView(binding.getRoot());
 
 		final String value;
@@ -34,8 +48,18 @@ public final class BlockContactDialog {
 			value =blockable.getJid().getDomain().toEscapedString();
 			res = isBlocked ? R.string.unblock_domain_text : R.string.block_domain_text;
 		} else {
-			int resBlockAction = blockable instanceof Conversation && ((Conversation) blockable).isWithStranger() ? R.string.block_stranger : R.string.action_block_contact;
-			builder.setTitle(isBlocked ? R.string.action_unblock_contact : resBlockAction);
+			if (isBlocked) {
+				builder.setTitle(R.string.action_unblock_contact);
+			} else if (serverMsgId != null) {
+				builder.setTitle(R.string.report_spam_and_block);
+            } else {
+                final int resBlockAction =
+                        blockable instanceof Conversation
+                                        && ((Conversation) blockable).isWithStranger()
+                                ? R.string.block_stranger
+                                : R.string.action_block_contact;
+                builder.setTitle(resBlockAction);
+			}
 			value = blockable.getJid().asBareJid().toEscapedString();
 			res = isBlocked ? R.string.unblock_contact_text : R.string.block_contact_text;
 		}
@@ -45,7 +69,7 @@ public final class BlockContactDialog {
 				xmppActivity.xmppConnectionService.sendUnblockRequest(blockable);
 			} else {
 				boolean toastShown = false;
-				if (xmppActivity.xmppConnectionService.sendBlockRequest(blockable, binding.reportSpam.isChecked())) {
+				if (xmppActivity.xmppConnectionService.sendBlockRequest(blockable, binding.reportSpam.isChecked(), serverMsgId)) {
 					Toast.makeText(xmppActivity, R.string.corresponding_conversations_closed, Toast.LENGTH_SHORT).show();
 					toastShown = true;
 				}
  
  
  
    
    @@ -87,7 +87,7 @@ public class BlocklistActivity extends AbstractSearchableListItemActivity implem
 
 		dialog.setOnEnterJidDialogPositiveListener((accountJid, contactJid) -> {
 			Blockable blockable = new RawBlockable(account, contactJid);
-			if (xmppConnectionService.sendBlockRequest(blockable, false)) {
+			if (xmppConnectionService.sendBlockRequest(blockable, false, null)) {
 				Toast.makeText(BlocklistActivity.this, R.string.corresponding_conversations_closed, Toast.LENGTH_SHORT).show();
 			}
 			return true;
  
  
  
    
    @@ -1308,6 +1308,7 @@ public class ConversationFragment extends XmppFragment
                                     || t instanceof HttpDownloadConnection);
             activity.getMenuInflater().inflate(R.menu.message_context, menu);
             menu.setHeaderTitle(R.string.message_options);
+            final MenuItem reportAndBlock = menu.findItem(R.id.action_report_and_block);
             MenuItem openWith = menu.findItem(R.id.open_with);
             MenuItem copyMessage = menu.findItem(R.id.copy_message);
             MenuItem copyLink = menu.findItem(R.id.copy_link);
@@ -1326,6 +1327,17 @@ public class ConversationFragment extends XmppFragment
                     m.getStatus() == Message.STATUS_SEND_FAILED
                             && m.getErrorMessage() != null
                             && !Message.ERROR_MESSAGE_CANCELLED.equals(m.getErrorMessage());
+            final Conversational conversational = m.getConversation();
+            if (m.getStatus() == Message.STATUS_RECEIVED && conversational instanceof Conversation c) {
+                final XmppConnection connection = c.getAccount().getXmppConnection();
+                if (c.isWithStranger()
+                        && m.getServerMsgId() != null
+                        && !c.isBlocked()
+                        && connection != null
+                        && connection.getFeatures().spamReporting()) {
+                    reportAndBlock.setVisible(true);
+                }
+            }
             if (!m.isFileOrImage()
                     && !encrypted
                     && !m.isGeoUri()
@@ -1449,6 +1461,9 @@ public class ConversationFragment extends XmppFragment
             case R.id.open_with:
                 openWith(selectedMessage);
                 return true;
+            case R.id.action_report_and_block:
+                reportMessage(selectedMessage);
+                return true;
             default:
                 return super.onContextItemSelected(item);
         }
@@ -2114,6 +2129,10 @@ public class ConversationFragment extends XmppFragment
         }
     }
 
+    private void reportMessage(final Message message) {
+        BlockContactDialog.show(activity, conversation.getContact(), message.getServerMsgId());
+    }
+
     private void showErrorMessage(final Message message) {
         AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity());
         builder.setTitle(R.string.error_message);
  
  
  
    
    @@ -67,4 +67,6 @@ public final class Namespace {
     public static final String OMEMO_DTLS_SRTP_VERIFICATION = "http://gultsch.de/xmpp/drafts/omemo/dlts-srtp-verification";
     public static final String JINGLE_TRANSPORT_ICE_OPTION = "http://gultsch.de/xmpp/drafts/jingle/transports/ice-udp/option";
     public static final String UNIFIED_PUSH = "http://gultsch.de/xmpp/drafts/unified-push";
+    public static final String REPORTING = "urn:xmpp:reporting:1";
+    public static final String REPORTING_REASON_SPAM = "urn:xmpp:reporting:spam";
 }
  
  
  
    
    @@ -2689,7 +2689,7 @@ public class XmppConnection implements Runnable {
         }
 
         public boolean spamReporting() {
-            return hasDiscoFeature(account.getDomain(), "urn:xmpp:reporting:reason:spam:0");
+            return hasDiscoFeature(account.getDomain(), Namespace.REPORTING);
         }
 
         public boolean flexibleOfflineMessageRetrieval() {
  
  
  
    
    @@ -1,6 +1,11 @@
 <?xml version="1.0" encoding="utf-8"?>
 <menu xmlns:android="http://schemas.android.com/apk/res/android">
 
+    <item
+        android:id="@+id/action_report_and_block"
+        android:title="@string/report_spam"
+        android:visible="false" />
+
     <item
         android:id="@+id/open_with"
         android:title="@string/open_with"
  
  
  
    
    @@ -1020,4 +1020,6 @@
     <string name="log_in">Log in</string>
     <string name="contact_uses_unverified_keys">Your contact uses unverified devices. Scan their 2D barcode to perform verification and impede active MITM attacks.</string>
     <string name="unverified_devices">You are using unverified devices. Scan the 2D barcode on your other devices to perform verification and impede active MITM attacks.</string>
+    <string name="report_spam">Report spam</string>
+    <string name="report_spam_and_block">Report spam and block spammer</string>
 </resources>