Detailed changes
  
  
    
    @@ -65,6 +65,7 @@ public final class Namespace {
     public static final String PARS = "urn:xmpp:pars:0";
     public static final String EASY_ONBOARDING_INVITE = "urn:xmpp:invite#invite";
     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 VCARD4 = "urn:ietf:params:xml:ns:vcard-4.0";
 }
  
  
  
    
    @@ -16,6 +16,7 @@ import com.google.common.collect.Collections2;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import com.google.common.primitives.Ints;
 import com.google.common.util.concurrent.FutureCallback;
@@ -242,6 +243,9 @@ public class JingleRtpConnection extends AbstractJingleConnection
             case CONTENT_REMOVE:
                 receiveContentRemove(jinglePacket);
                 break;
+            case CONTENT_MODIFY:
+                receiveContentModify(jinglePacket);
+                break;
             default:
                 respondOk(jinglePacket);
                 Log.d(
@@ -516,6 +520,14 @@ public class JingleRtpConnection extends AbstractJingleConnection
                         + ContentAddition.summary(receivedContentAccept));
     }
 
+    private void receiveContentModify(final JinglePacket jinglePacket) {
+        final Map<String, Content.Senders> modification =
+                Maps.transformEntries(
+                        jinglePacket.getJingleContents(), (key, value) -> value.getSenders());
+        respondOk(jinglePacket);
+        Log.d(Config.LOGTAG, "receiveContentModification(" + modification + ")");
+    }
+
     private void receiveContentReject(final JinglePacket jinglePacket) {
         final RtpContentMap receivedContentReject;
         try {
  
  
  
    
    @@ -11,23 +11,26 @@ import com.google.common.base.Strings;
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ImmutableList;
 
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.xml.Namespace;
 import eu.siacs.conversations.xmpp.jingle.stanzas.Group;
 import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo;
 import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription;
 
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
 public class SessionDescription {
 
     public static final String LINE_DIVIDER = "\r\n";
     private static final String HARDCODED_MEDIA_PROTOCOL =
             "UDP/TLS/RTP/SAVPF"; // probably only true for DTLS-SRTP aka when we have a fingerprint
     private static final int HARDCODED_MEDIA_PORT = 9;
-    private static final String HARDCODED_ICE_OPTIONS = "trickle";
+    private static final Collection<String> HARDCODED_ICE_OPTIONS =
+            Collections.singleton("trickle");
     private static final String HARDCODED_CONNECTION = "IN IP4 0.0.0.0";
 
     public final int version;
@@ -128,7 +131,8 @@ public class SessionDescription {
         return sessionDescriptionBuilder.createSessionDescription();
     }
 
-    public static SessionDescription of(final RtpContentMap contentMap, final boolean isInitiatorContentMap) {
+    public static SessionDescription of(
+            final RtpContentMap contentMap, final boolean isInitiatorContentMap) {
         final SessionDescriptionBuilder sessionDescriptionBuilder = new SessionDescriptionBuilder();
         final ArrayListMultimap<String, String> attributeMap = ArrayListMultimap.create();
         final ImmutableList.Builder<Media> mediaListBuilder = new ImmutableList.Builder<>();
@@ -166,7 +170,10 @@ public class SessionDescription {
             }
             checkNoWhitespace(pwd, "pwd value must not contain any whitespaces");
             mediaAttributes.put("ice-pwd", pwd);
-            mediaAttributes.put("ice-options", HARDCODED_ICE_OPTIONS);
+            final List<String> negotiatedIceOptions = transport.getIceOptions();
+            final Collection<String> iceOptions =
+                    negotiatedIceOptions.isEmpty() ? HARDCODED_ICE_OPTIONS : negotiatedIceOptions;
+            mediaAttributes.put("ice-options", Joiner.on(' ').join(iceOptions));
             final IceUdpTransportInfo.Fingerprint fingerprint = transport.getFingerprint();
             if (fingerprint != null) {
                 mediaAttributes.put(
@@ -297,7 +304,8 @@ public class SessionDescription {
 
             mediaAttributes.put("mid", name);
 
-            mediaAttributes.put(descriptionTransport.senders.asMediaAttribute(isInitiatorContentMap), "");
+            mediaAttributes.put(
+                    descriptionTransport.senders.asMediaAttribute(isInitiatorContentMap), "");
             if (description.hasChild("rtcp-mux", Namespace.JINGLE_APPS_RTP) || group != null) {
                 mediaAttributes.put("rtcp-mux", "");
             }
  
  
  
    
    @@ -1,17 +1,23 @@
 package eu.siacs.conversations.xmpp.jingle.stanzas;
 
+import android.util.Log;
+
 import androidx.annotation.NonNull;
 
 import com.google.common.base.Joiner;
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Objects;
 import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
 import com.google.common.base.Strings;
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.Collections2;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Hashtable;
 import java.util.LinkedHashMap;
@@ -20,6 +26,7 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.UUID;
 
+import eu.siacs.conversations.Config;
 import eu.siacs.conversations.xml.Element;
 import eu.siacs.conversations.xml.Namespace;
 import eu.siacs.conversations.xmpp.jingle.SessionDescription;
@@ -59,6 +66,9 @@ public class IceUdpTransportInfo extends GenericTransportInfo {
         if (fingerprint != null) {
             iceUdpTransportInfo.addChild(fingerprint);
         }
+        for (final String iceOption : IceOption.of(media)) {
+            iceUdpTransportInfo.addChild(new IceOption(iceOption));
+        }
         return iceUdpTransportInfo;
     }
 
@@ -76,6 +86,16 @@ public class IceUdpTransportInfo extends GenericTransportInfo {
         return fingerprint == null ? null : Fingerprint.upgrade(fingerprint);
     }
 
+    public List<String> getIceOptions() {
+        final ImmutableList.Builder<String> optionBuilder = new ImmutableList.Builder<>();
+        for(final Element child : this.children) {
+            if (Namespace.JINGLE_TRANSPORT_ICE_OPTION.equals(child.getNamespace()) && IceOption.WELL_KNOWN.contains(child.getName())) {
+                optionBuilder.add(child.getName());
+            }
+        }
+        return optionBuilder.build();
+    }
+
     public Credentials getCredentials() {
         final String ufrag = this.getAttribute("ufrag");
         final String password = this.getAttribute("pwd");
@@ -408,4 +428,29 @@ public class IceUdpTransportInfo extends GenericTransportInfo {
             throw new IllegalStateException(this.name() + " can not be flipped");
         }
     }
+
+    public static class IceOption extends Element {
+
+        public static final List<String> WELL_KNOWN = Arrays.asList("trickle", "renomination");
+
+        public IceOption(final String name) {
+            super(name, Namespace.JINGLE_TRANSPORT_ICE_OPTION);
+        }
+
+        public static Collection<String> of(SessionDescription.Media media) {
+            final String iceOptions = Iterables.getFirst(media.attributes.get("ice-options"), null);
+            if (Strings.isNullOrEmpty(iceOptions)) {
+                return Collections.emptyList();
+            }
+            final ImmutableList.Builder<String> optionBuilder = new ImmutableList.Builder<>();
+            for (final String iceOption : Splitter.on(' ').split(iceOptions)) {
+                if (WELL_KNOWN.contains(iceOption)) {
+                    optionBuilder.add(iceOption);
+                } else {
+                    Log.w(Config.LOGTAG, "unrecognized ice option: " + iceOption);
+                }
+            }
+            return optionBuilder.build();
+        }
+    }
 }