remove security check that ensures rtp connection was properly finished

Daniel Gultsch created

this only causes race conditions

Change summary

src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java                |   1 
src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractJingleConnection.java |   3 
src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java  | 535 
src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java      | 652 
4 files changed, 795 insertions(+), 396 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java πŸ”—

@@ -646,7 +646,6 @@ public class RtpSessionActivity extends XmppActivity
         final RtpEndUserState currentState = requireRtpConnection().getEndUserState();
         final boolean verified = requireRtpConnection().isVerified();
         if (currentState == RtpEndUserState.ENDED) {
-            reference.get().throwStateTransitionException();
             finish();
             return true;
         }

src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractJingleConnection.java πŸ”—

@@ -1,5 +1,7 @@
 package eu.siacs.conversations.xmpp.jingle;
 
+import androidx.annotation.NonNull;
+
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Objects;
 import com.google.common.base.Preconditions;
@@ -110,6 +112,7 @@ public abstract class AbstractJingleConnection {
         }
 
         @Override
+        @NonNull
         public String toString() {
             return MoreObjects.toStringHelper(this)
                     .add("account", account.getJid())

src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java πŸ”—

@@ -52,14 +52,16 @@ import eu.siacs.conversations.xmpp.stanzas.IqPacket;
 import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
 
 public class JingleConnectionManager extends AbstractConnectionManager {
-    static final ScheduledExecutorService SCHEDULED_EXECUTOR_SERVICE = Executors.newSingleThreadScheduledExecutor();
+    static final ScheduledExecutorService SCHEDULED_EXECUTOR_SERVICE =
+            Executors.newSingleThreadScheduledExecutor();
     final ToneManager toneManager;
-    private final HashMap<RtpSessionProposal, DeviceDiscoveryState> rtpSessionProposals = new HashMap<>();
-    private final ConcurrentHashMap<AbstractJingleConnection.Id, AbstractJingleConnection> connections = new ConcurrentHashMap<>();
+    private final HashMap<RtpSessionProposal, DeviceDiscoveryState> rtpSessionProposals =
+            new HashMap<>();
+    private final ConcurrentHashMap<AbstractJingleConnection.Id, AbstractJingleConnection>
+            connections = new ConcurrentHashMap<>();
 
-    private final Cache<PersistableSessionId, TerminatedRtpSession> terminatedSessions = CacheBuilder.newBuilder()
-            .expireAfterWrite(24, TimeUnit.HOURS)
-            .build();
+    private final Cache<PersistableSessionId, TerminatedRtpSession> terminatedSessions =
+            CacheBuilder.newBuilder().expireAfterWrite(24, TimeUnit.HOURS).build();
 
     private final HashMap<Jid, JingleCandidate> primaryCandidates = new HashMap<>();
 
@@ -87,17 +89,31 @@ public class JingleConnectionManager extends AbstractConnectionManager {
         } else if (packet.getAction() == JinglePacket.Action.SESSION_INITIATE) {
             final Jid from = packet.getFrom();
             final Content content = packet.getJingleContent();
-            final String descriptionNamespace = content == null ? null : content.getDescriptionNamespace();
+            final String descriptionNamespace =
+                    content == null ? null : content.getDescriptionNamespace();
             final AbstractJingleConnection connection;
             if (FileTransferDescription.NAMESPACES.contains(descriptionNamespace)) {
                 connection = new JingleFileTransferConnection(this, id, from);
-            } else if (Namespace.JINGLE_APPS_RTP.equals(descriptionNamespace) && isUsingClearNet(account)) {
-                final boolean sessionEnded = this.terminatedSessions.asMap().containsKey(PersistableSessionId.of(id));
-                final boolean stranger = isWithStrangerAndStrangerNotificationsAreOff(account, id.with);
+            } else if (Namespace.JINGLE_APPS_RTP.equals(descriptionNamespace)
+                    && isUsingClearNet(account)) {
+                final boolean sessionEnded =
+                        this.terminatedSessions.asMap().containsKey(PersistableSessionId.of(id));
+                final boolean stranger =
+                        isWithStrangerAndStrangerNotificationsAreOff(account, id.with);
                 if (isBusy() || sessionEnded || stranger) {
-                    Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": rejected session with " + id.with + " because busy. sessionEnded=" + sessionEnded + ", stranger=" + stranger);
-                    mXmppConnectionService.sendIqPacket(account, packet.generateResponse(IqPacket.TYPE.RESULT), null);
-                    final JinglePacket sessionTermination = new JinglePacket(JinglePacket.Action.SESSION_TERMINATE, id.sessionId);
+                    Log.d(
+                            Config.LOGTAG,
+                            id.account.getJid().asBareJid()
+                                    + ": rejected session with "
+                                    + id.with
+                                    + " because busy. sessionEnded="
+                                    + sessionEnded
+                                    + ", stranger="
+                                    + stranger);
+                    mXmppConnectionService.sendIqPacket(
+                            account, packet.generateResponse(IqPacket.TYPE.RESULT), null);
+                    final JinglePacket sessionTermination =
+                            new JinglePacket(JinglePacket.Action.SESSION_TERMINATE, id.sessionId);
                     sessionTermination.setTo(id.with);
                     sessionTermination.setReason(Reason.BUSY, null);
                     mXmppConnectionService.sendIqPacket(account, sessionTermination, null);
@@ -105,7 +121,8 @@ public class JingleConnectionManager extends AbstractConnectionManager {
                 }
                 connection = new JingleRtpConnection(this, id, from);
             } else {
-                respondWithJingleError(account, packet, "unsupported-info", "feature-not-implemented", "cancel");
+                respondWithJingleError(
+                        account, packet, "unsupported-info", "feature-not-implemented", "cancel");
                 return;
             }
             connections.put(id, connection);
@@ -136,7 +153,8 @@ public class JingleConnectionManager extends AbstractConnectionManager {
         synchronized (this.rtpSessionProposals) {
             return this.rtpSessionProposals.containsValue(DeviceDiscoveryState.DISCOVERED)
                     || this.rtpSessionProposals.containsValue(DeviceDiscoveryState.SEARCHING)
-                    || this.rtpSessionProposals.containsValue(DeviceDiscoveryState.SEARCHING_ACKNOWLEDGED);
+                    || this.rtpSessionProposals.containsValue(
+                            DeviceDiscoveryState.SEARCHING_ACKNOWLEDGED);
         }
     }
 
@@ -152,14 +170,17 @@ public class JingleConnectionManager extends AbstractConnectionManager {
         }
     }
 
-    private Optional<RtpSessionProposal> findMatchingSessionProposal(final Account account, final Jid with, final Set<Media> media) {
+    private Optional<RtpSessionProposal> findMatchingSessionProposal(
+            final Account account, final Jid with, final Set<Media> media) {
         synchronized (this.rtpSessionProposals) {
-            for (Map.Entry<RtpSessionProposal, DeviceDiscoveryState> entry : this.rtpSessionProposals.entrySet()) {
+            for (Map.Entry<RtpSessionProposal, DeviceDiscoveryState> entry :
+                    this.rtpSessionProposals.entrySet()) {
                 final RtpSessionProposal proposal = entry.getKey();
                 final DeviceDiscoveryState state = entry.getValue();
-                final boolean openProposal = state == DeviceDiscoveryState.DISCOVERED
-                        || state == DeviceDiscoveryState.SEARCHING
-                        || state == DeviceDiscoveryState.SEARCHING_ACKNOWLEDGED;
+                final boolean openProposal =
+                        state == DeviceDiscoveryState.DISCOVERED
+                                || state == DeviceDiscoveryState.SEARCHING
+                                || state == DeviceDiscoveryState.SEARCHING_ACKNOWLEDGED;
                 if (openProposal
                         && proposal.account == account
                         && proposal.with.equals(with.asBareJid())
@@ -171,7 +192,8 @@ public class JingleConnectionManager extends AbstractConnectionManager {
         return Optional.absent();
     }
 
-    private boolean hasMatchingRtpSession(final Account account, final Jid with, final Set<Media> media) {
+    private boolean hasMatchingRtpSession(
+            final Account account, final Jid with, final Set<Media> media) {
         for (AbstractJingleConnection connection : this.connections.values()) {
             if (connection instanceof JingleRtpConnection) {
                 final JingleRtpConnection rtpConnection = (JingleRtpConnection) connection;
@@ -189,7 +211,8 @@ public class JingleConnectionManager extends AbstractConnectionManager {
     }
 
     private boolean isWithStrangerAndStrangerNotificationsAreOff(final Account account, Jid with) {
-        final boolean notifyForStrangers = mXmppConnectionService.getNotificationService().notificationsFromStrangers();
+        final boolean notifyForStrangers =
+                mXmppConnectionService.getNotificationService().notificationsFromStrangers();
         if (notifyForStrangers) {
             return false;
         }
@@ -197,11 +220,17 @@ public class JingleConnectionManager extends AbstractConnectionManager {
         return !contact.showInContactList();
     }
 
-    ScheduledFuture<?> schedule(final Runnable runnable, final long delay, final TimeUnit timeUnit) {
+    ScheduledFuture<?> schedule(
+            final Runnable runnable, final long delay, final TimeUnit timeUnit) {
         return SCHEDULED_EXECUTOR_SERVICE.schedule(runnable, delay, timeUnit);
     }
 
-    void respondWithJingleError(final Account account, final IqPacket original, String jingleCondition, String condition, String conditionType) {
+    void respondWithJingleError(
+            final Account account,
+            final IqPacket original,
+            String jingleCondition,
+            String condition,
+            String conditionType) {
         final IqPacket response = original.generateResponse(IqPacket.TYPE.ERROR);
         final Element error = response.addChild("error");
         error.setAttribute("type", conditionType);
@@ -210,7 +239,14 @@ public class JingleConnectionManager extends AbstractConnectionManager {
         account.getXmppConnection().sendIqPacket(response, null);
     }
 
-    public void deliverMessage(final Account account, final Jid to, final Jid from, final Element message, String remoteMsgId, String serverMsgId, long timestamp) {
+    public void deliverMessage(
+            final Account account,
+            final Jid to,
+            final Jid from,
+            final Element message,
+            String remoteMsgId,
+            String serverMsgId,
+            long timestamp) {
         Preconditions.checkArgument(Namespace.JINGLE_MESSAGE.equals(message.getNamespace()));
         final String sessionId = message.getAttribute("id");
         if (sessionId == null) {
@@ -244,16 +280,24 @@ public class JingleConnectionManager extends AbstractConnectionManager {
         final AbstractJingleConnection existingJingleConnection = connections.get(id);
         if (existingJingleConnection != null) {
             if (existingJingleConnection instanceof JingleRtpConnection) {
-                ((JingleRtpConnection) existingJingleConnection).deliveryMessage(from, message, serverMsgId, timestamp);
+                ((JingleRtpConnection) existingJingleConnection)
+                        .deliveryMessage(from, message, serverMsgId, timestamp);
             } else {
-                Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": " + existingJingleConnection.getClass().getName() + " does not support jingle messages");
+                Log.d(
+                        Config.LOGTAG,
+                        account.getJid().asBareJid()
+                                + ": "
+                                + existingJingleConnection.getClass().getName()
+                                + " does not support jingle messages");
             }
             return;
         }
 
         if (fromSelf) {
             if ("proceed".equals(message.getName())) {
-                final Conversation c = mXmppConnectionService.findOrCreateConversation(account, id.with, false, false);
+                final Conversation c =
+                        mXmppConnectionService.findOrCreateConversation(
+                                account, id.with, false, false);
                 final Message previousBusy = c.findRtpSession(sessionId, Message.STATUS_RECEIVED);
                 if (previousBusy != null) {
                     previousBusy.setBody(new RtpSessionStatus(true, 0).toString());
@@ -262,84 +306,138 @@ public class JingleConnectionManager extends AbstractConnectionManager {
                     }
                     previousBusy.setTime(timestamp);
                     mXmppConnectionService.updateMessage(previousBusy, true);
-                    Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": updated previous busy because call got picked up by another device");
+                    Log.d(
+                            Config.LOGTAG,
+                            id.account.getJid().asBareJid()
+                                    + ": updated previous busy because call got picked up by another device");
                     return;
                 }
             }
-            //TODO handle reject for cases where we don’t have carbon copies (normally reject is to be sent to own bare jid as well)
-            Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ignore jingle message from self");
+            // TODO handle reject for cases where we don’t have carbon copies (normally reject is to
+            // be sent to own bare jid as well)
+            Log.d(
+                    Config.LOGTAG,
+                    account.getJid().asBareJid() + ": ignore jingle message from self");
             return;
         }
 
         if ("propose".equals(message.getName())) {
             final Propose propose = Propose.upgrade(message);
             final List<GenericDescription> descriptions = propose.getDescriptions();
-            final Collection<RtpDescription> rtpDescriptions = Collections2.transform(
-                    Collections2.filter(descriptions, d -> d instanceof RtpDescription),
-                    input -> (RtpDescription) input
-            );
-            if (rtpDescriptions.size() > 0 && rtpDescriptions.size() == descriptions.size() && isUsingClearNet(account)) {
-                final Collection<Media> media = Collections2.transform(rtpDescriptions, RtpDescription::getMedia);
+            final Collection<RtpDescription> rtpDescriptions =
+                    Collections2.transform(
+                            Collections2.filter(descriptions, d -> d instanceof RtpDescription),
+                            input -> (RtpDescription) input);
+            if (rtpDescriptions.size() > 0
+                    && rtpDescriptions.size() == descriptions.size()
+                    && isUsingClearNet(account)) {
+                final Collection<Media> media =
+                        Collections2.transform(rtpDescriptions, RtpDescription::getMedia);
                 if (media.contains(Media.UNKNOWN)) {
-                    Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": encountered unknown media in session proposal. " + propose);
+                    Log.d(
+                            Config.LOGTAG,
+                            account.getJid().asBareJid()
+                                    + ": encountered unknown media in session proposal. "
+                                    + propose);
                     return;
                 }
-                final Optional<RtpSessionProposal> matchingSessionProposal = findMatchingSessionProposal(account, id.with, ImmutableSet.copyOf(media));
+                final Optional<RtpSessionProposal> matchingSessionProposal =
+                        findMatchingSessionProposal(account, id.with, ImmutableSet.copyOf(media));
                 if (matchingSessionProposal.isPresent()) {
                     final String ourSessionId = matchingSessionProposal.get().sessionId;
                     final String theirSessionId = id.sessionId;
                     if (ComparisonChain.start()
-                            .compare(ourSessionId, theirSessionId)
-                            .compare(account.getJid().toEscapedString(), id.with.toEscapedString())
-                            .result() > 0) {
-                        Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": our session lost tie break. automatically accepting their session. winning Session=" + theirSessionId);
-                        //TODO a retract for this reason should probably include some indication of tie break
+                                    .compare(ourSessionId, theirSessionId)
+                                    .compare(
+                                            account.getJid().toEscapedString(),
+                                            id.with.toEscapedString())
+                                    .result()
+                            > 0) {
+                        Log.d(
+                                Config.LOGTAG,
+                                account.getJid().asBareJid()
+                                        + ": our session lost tie break. automatically accepting their session. winning Session="
+                                        + theirSessionId);
+                        // TODO a retract for this reason should probably include some indication of
+                        // tie break
                         retractSessionProposal(matchingSessionProposal.get());
-                        final JingleRtpConnection rtpConnection = new JingleRtpConnection(this, id, from);
+                        final JingleRtpConnection rtpConnection =
+                                new JingleRtpConnection(this, id, from);
                         this.connections.put(id, rtpConnection);
                         rtpConnection.setProposedMedia(ImmutableSet.copyOf(media));
                         rtpConnection.deliveryMessage(from, message, serverMsgId, timestamp);
                     } else {
-                        Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": our session won tie break. waiting for other party to accept. winningSession=" + ourSessionId);
+                        Log.d(
+                                Config.LOGTAG,
+                                account.getJid().asBareJid()
+                                        + ": our session won tie break. waiting for other party to accept. winningSession="
+                                        + ourSessionId);
                     }
                     return;
                 }
-                final boolean stranger = isWithStrangerAndStrangerNotificationsAreOff(account, id.with);
+                final boolean stranger =
+                        isWithStrangerAndStrangerNotificationsAreOff(account, id.with);
                 if (isBusy() || stranger) {
-                    writeLogMissedIncoming(account, id.with.asBareJid(), id.sessionId, serverMsgId, timestamp);
+                    writeLogMissedIncoming(
+                            account, id.with.asBareJid(), id.sessionId, serverMsgId, timestamp);
                     if (stranger) {
-                        Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": ignoring call proposal from stranger " + id.with);
+                        Log.d(
+                                Config.LOGTAG,
+                                id.account.getJid().asBareJid()
+                                        + ": ignoring call proposal from stranger "
+                                        + id.with);
                         return;
                     }
                     final int activeDevices = account.activeDevicesWithRtpCapability();
                     Log.d(Config.LOGTAG, "active devices with rtp capability: " + activeDevices);
                     if (activeDevices == 0) {
-                        final MessagePacket reject = mXmppConnectionService.getMessageGenerator().sessionReject(from, sessionId);
+                        final MessagePacket reject =
+                                mXmppConnectionService
+                                        .getMessageGenerator()
+                                        .sessionReject(from, sessionId);
                         mXmppConnectionService.sendMessagePacket(account, reject);
                     } else {
-                        Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": ignoring proposal because busy on this device but there are other devices");
+                        Log.d(
+                                Config.LOGTAG,
+                                id.account.getJid().asBareJid()
+                                        + ": ignoring proposal because busy on this device but there are other devices");
                     }
                 } else {
-                    final JingleRtpConnection rtpConnection = new JingleRtpConnection(this, id, from);
+                    final JingleRtpConnection rtpConnection =
+                            new JingleRtpConnection(this, id, from);
                     this.connections.put(id, rtpConnection);
                     rtpConnection.setProposedMedia(ImmutableSet.copyOf(media));
                     rtpConnection.deliveryMessage(from, message, serverMsgId, timestamp);
                 }
             } else {
-                Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to react to proposed session with " + rtpDescriptions.size() + " rtp descriptions of " + descriptions.size() + " total descriptions");
+                Log.d(
+                        Config.LOGTAG,
+                        account.getJid().asBareJid()
+                                + ": unable to react to proposed session with "
+                                + rtpDescriptions.size()
+                                + " rtp descriptions of "
+                                + descriptions.size()
+                                + " total descriptions");
             }
         } else if (addressedDirectly && "proceed".equals(message.getName())) {
             synchronized (rtpSessionProposals) {
-                final RtpSessionProposal proposal = getRtpSessionProposal(account, from.asBareJid(), sessionId);
+                final RtpSessionProposal proposal =
+                        getRtpSessionProposal(account, from.asBareJid(), sessionId);
                 if (proposal != null) {
                     rtpSessionProposals.remove(proposal);
-                    final JingleRtpConnection rtpConnection = new JingleRtpConnection(this, id, account.getJid());
+                    final JingleRtpConnection rtpConnection =
+                            new JingleRtpConnection(this, id, account.getJid());
                     rtpConnection.setProposedMedia(proposal.media);
                     this.connections.put(id, rtpConnection);
                     rtpConnection.transitionOrThrow(AbstractJingleConnection.State.PROPOSED);
                     rtpConnection.deliveryMessage(from, message, serverMsgId, timestamp);
                 } else {
-                    Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": no rtp session proposal found for " + from + " to deliver proceed");
+                    Log.d(
+                            Config.LOGTAG,
+                            account.getJid().asBareJid()
+                                    + ": no rtp session proposal found for "
+                                    + from
+                                    + " to deliver proceed");
                     if (remoteMsgId == null) {
                         return;
                     }
@@ -355,63 +453,77 @@ public class JingleConnectionManager extends AbstractConnectionManager {
                 }
             }
         } else if (addressedDirectly && "reject".equals(message.getName())) {
-            final RtpSessionProposal proposal = getRtpSessionProposal(account, from.asBareJid(), sessionId);
+            final RtpSessionProposal proposal =
+                    getRtpSessionProposal(account, from.asBareJid(), sessionId);
             synchronized (rtpSessionProposals) {
                 if (proposal != null && rtpSessionProposals.remove(proposal) != null) {
-                    writeLogMissedOutgoing(account, proposal.with, proposal.sessionId, serverMsgId, timestamp);
+                    writeLogMissedOutgoing(
+                            account, proposal.with, proposal.sessionId, serverMsgId, timestamp);
                     toneManager.transition(RtpEndUserState.DECLINED_OR_BUSY, proposal.media);
-                    mXmppConnectionService.notifyJingleRtpConnectionUpdate(account, proposal.with, proposal.sessionId, RtpEndUserState.DECLINED_OR_BUSY);
+                    mXmppConnectionService.notifyJingleRtpConnectionUpdate(
+                            account,
+                            proposal.with,
+                            proposal.sessionId,
+                            RtpEndUserState.DECLINED_OR_BUSY);
                 } else {
-                    Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": no rtp session proposal found for " + from + " to deliver reject");
+                    Log.d(
+                            Config.LOGTAG,
+                            account.getJid().asBareJid()
+                                    + ": no rtp session proposal found for "
+                                    + from
+                                    + " to deliver reject");
                 }
             }
         } else {
-            Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": retrieved out of order jingle message" + message);
+            Log.d(
+                    Config.LOGTAG,
+                    account.getJid().asBareJid()
+                            + ": retrieved out of order jingle message"
+                            + message);
         }
-
     }
 
-    private RtpSessionProposal getRtpSessionProposal(final Account account, Jid from, String sessionId) {
+    private RtpSessionProposal getRtpSessionProposal(
+            final Account account, Jid from, String sessionId) {
         for (RtpSessionProposal rtpSessionProposal : rtpSessionProposals.keySet()) {
-            if (rtpSessionProposal.sessionId.equals(sessionId) && rtpSessionProposal.with.equals(from) && rtpSessionProposal.account.getJid().equals(account.getJid())) {
+            if (rtpSessionProposal.sessionId.equals(sessionId)
+                    && rtpSessionProposal.with.equals(from)
+                    && rtpSessionProposal.account.getJid().equals(account.getJid())) {
                 return rtpSessionProposal;
             }
         }
         return null;
     }
 
-    private void writeLogMissedOutgoing(final Account account, Jid with, final String sessionId, String serverMsgId, long timestamp) {
-        final Conversation conversation = mXmppConnectionService.findOrCreateConversation(
-                account,
-                with.asBareJid(),
-                false,
-                false
-        );
-        final Message message = new Message(
-                conversation,
-                Message.STATUS_SEND,
-                Message.TYPE_RTP_SESSION,
-                sessionId
-        );
+    private void writeLogMissedOutgoing(
+            final Account account,
+            Jid with,
+            final String sessionId,
+            String serverMsgId,
+            long timestamp) {
+        final Conversation conversation =
+                mXmppConnectionService.findOrCreateConversation(
+                        account, with.asBareJid(), false, false);
+        final Message message =
+                new Message(conversation, Message.STATUS_SEND, Message.TYPE_RTP_SESSION, sessionId);
         message.setBody(new RtpSessionStatus(false, 0).toString());
         message.setServerMsgId(serverMsgId);
         message.setTime(timestamp);
         writeMessage(message);
     }
 
-    private void writeLogMissedIncoming(final Account account, Jid with, final String sessionId, String serverMsgId, long timestamp) {
-        final Conversation conversation = mXmppConnectionService.findOrCreateConversation(
-                account,
-                with.asBareJid(),
-                false,
-                false
-        );
-        final Message message = new Message(
-                conversation,
-                Message.STATUS_RECEIVED,
-                Message.TYPE_RTP_SESSION,
-                sessionId
-        );
+    private void writeLogMissedIncoming(
+            final Account account,
+            Jid with,
+            final String sessionId,
+            String serverMsgId,
+            long timestamp) {
+        final Conversation conversation =
+                mXmppConnectionService.findOrCreateConversation(
+                        account, with.asBareJid(), false, false);
+        final Message message =
+                new Message(
+                        conversation, Message.STATUS_RECEIVED, Message.TYPE_RTP_SESSION, sessionId);
         message.setBody(new RtpSessionStatus(false, 0).toString());
         message.setServerMsgId(serverMsgId);
         message.setTime(timestamp);
@@ -430,34 +542,41 @@ public class JingleConnectionManager extends AbstractConnectionManager {
     }
 
     public void startJingleFileTransfer(final Message message) {
-        Preconditions.checkArgument(message.isFileOrImage(), "Message is not of type file or image");
+        Preconditions.checkArgument(
+                message.isFileOrImage(), "Message is not of type file or image");
         final Transferable old = message.getTransferable();
         if (old != null) {
             old.cancel();
         }
         final Account account = message.getConversation().getAccount();
         final AbstractJingleConnection.Id id = AbstractJingleConnection.Id.of(message);
-        final JingleFileTransferConnection connection = new JingleFileTransferConnection(this, id, account.getJid());
+        final JingleFileTransferConnection connection =
+                new JingleFileTransferConnection(this, id, account.getJid());
         mXmppConnectionService.markMessage(message, Message.STATUS_WAITING);
         this.connections.put(id, connection);
         connection.init(message);
     }
 
     public Optional<OngoingRtpSession> getOngoingRtpConnection(final Contact contact) {
-        for (final Map.Entry<AbstractJingleConnection.Id, AbstractJingleConnection> entry : this.connections.entrySet()) {
+        for (final Map.Entry<AbstractJingleConnection.Id, AbstractJingleConnection> entry :
+                this.connections.entrySet()) {
             if (entry.getValue() instanceof JingleRtpConnection) {
                 final AbstractJingleConnection.Id id = entry.getKey();
-                if (id.account == contact.getAccount() && id.with.asBareJid().equals(contact.getJid().asBareJid())) {
+                if (id.account == contact.getAccount()
+                        && id.with.asBareJid().equals(contact.getJid().asBareJid())) {
                     return Optional.of(id);
                 }
             }
         }
         synchronized (this.rtpSessionProposals) {
-            for (Map.Entry<RtpSessionProposal, DeviceDiscoveryState> entry : this.rtpSessionProposals.entrySet()) {
+            for (Map.Entry<RtpSessionProposal, DeviceDiscoveryState> entry :
+                    this.rtpSessionProposals.entrySet()) {
                 RtpSessionProposal proposal = entry.getKey();
-                if (proposal.account == contact.getAccount() && contact.getJid().asBareJid().equals(proposal.with)) {
+                if (proposal.account == contact.getAccount()
+                        && contact.getJid().asBareJid().equals(proposal.with)) {
                     final DeviceDiscoveryState preexistingState = entry.getValue();
-                    if (preexistingState != null && preexistingState != DeviceDiscoveryState.FAILED) {
+                    if (preexistingState != null
+                            && preexistingState != DeviceDiscoveryState.FAILED) {
                         return Optional.of(proposal);
                     }
                 }
@@ -473,7 +592,8 @@ public class JingleConnectionManager extends AbstractConnectionManager {
     void finishConnectionOrThrow(final AbstractJingleConnection connection) {
         final AbstractJingleConnection.Id id = connection.getId();
         if (this.connections.remove(id) == null) {
-            throw new IllegalStateException(String.format("Unable to finish connection with id=%s", id.toString()));
+            throw new IllegalStateException(
+                    String.format("Unable to finish connection with id=%s", id.toString()));
         }
     }
 
@@ -492,49 +612,70 @@ public class JingleConnectionManager extends AbstractConnectionManager {
         return firedUpdates;
     }
 
-    void getPrimaryCandidate(final Account account, final boolean initiator, final OnPrimaryCandidateFound listener) {
+    void getPrimaryCandidate(
+            final Account account,
+            final boolean initiator,
+            final OnPrimaryCandidateFound listener) {
         if (Config.DISABLE_PROXY_LOOKUP) {
             listener.onPrimaryCandidateFound(false, null);
             return;
         }
         if (!this.primaryCandidates.containsKey(account.getJid().asBareJid())) {
-            final Jid proxy = account.getXmppConnection().findDiscoItemByFeature(Namespace.BYTE_STREAMS);
+            final Jid proxy =
+                    account.getXmppConnection().findDiscoItemByFeature(Namespace.BYTE_STREAMS);
             if (proxy != null) {
                 IqPacket iq = new IqPacket(IqPacket.TYPE.GET);
                 iq.setTo(proxy);
                 iq.query(Namespace.BYTE_STREAMS);
-                account.getXmppConnection().sendIqPacket(iq, new OnIqPacketReceived() {
-
-                    @Override
-                    public void onIqPacketReceived(Account account, IqPacket packet) {
-                        final Element streamhost = packet.query().findChild("streamhost", Namespace.BYTE_STREAMS);
-                        final String host = streamhost == null ? null : streamhost.getAttribute("host");
-                        final String port = streamhost == null ? null : streamhost.getAttribute("port");
-                        if (host != null && port != null) {
-                            try {
-                                JingleCandidate candidate = new JingleCandidate(nextRandomId(), true);
-                                candidate.setHost(host);
-                                candidate.setPort(Integer.parseInt(port));
-                                candidate.setType(JingleCandidate.TYPE_PROXY);
-                                candidate.setJid(proxy);
-                                candidate.setPriority(655360 + (initiator ? 30 : 0));
-                                primaryCandidates.put(account.getJid().asBareJid(), candidate);
-                                listener.onPrimaryCandidateFound(true, candidate);
-                            } catch (final NumberFormatException e) {
-                                listener.onPrimaryCandidateFound(false, null);
-                            }
-                        } else {
-                            listener.onPrimaryCandidateFound(false, null);
-                        }
-                    }
-                });
+                account.getXmppConnection()
+                        .sendIqPacket(
+                                iq,
+                                new OnIqPacketReceived() {
+
+                                    @Override
+                                    public void onIqPacketReceived(
+                                            Account account, IqPacket packet) {
+                                        final Element streamhost =
+                                                packet.query()
+                                                        .findChild(
+                                                                "streamhost",
+                                                                Namespace.BYTE_STREAMS);
+                                        final String host =
+                                                streamhost == null
+                                                        ? null
+                                                        : streamhost.getAttribute("host");
+                                        final String port =
+                                                streamhost == null
+                                                        ? null
+                                                        : streamhost.getAttribute("port");
+                                        if (host != null && port != null) {
+                                            try {
+                                                JingleCandidate candidate =
+                                                        new JingleCandidate(nextRandomId(), true);
+                                                candidate.setHost(host);
+                                                candidate.setPort(Integer.parseInt(port));
+                                                candidate.setType(JingleCandidate.TYPE_PROXY);
+                                                candidate.setJid(proxy);
+                                                candidate.setPriority(
+                                                        655360 + (initiator ? 30 : 0));
+                                                primaryCandidates.put(
+                                                        account.getJid().asBareJid(), candidate);
+                                                listener.onPrimaryCandidateFound(true, candidate);
+                                            } catch (final NumberFormatException e) {
+                                                listener.onPrimaryCandidateFound(false, null);
+                                            }
+                                        } else {
+                                            listener.onPrimaryCandidateFound(false, null);
+                                        }
+                                    }
+                                });
             } else {
                 listener.onPrimaryCandidateFound(false, null);
             }
 
         } else {
-            listener.onPrimaryCandidateFound(true,
-                    this.primaryCandidates.get(account.getJid().asBareJid()));
+            listener.onPrimaryCandidateFound(
+                    true, this.primaryCandidates.get(account.getJid().asBareJid()));
         }
     }
 
@@ -556,64 +697,77 @@ public class JingleConnectionManager extends AbstractConnectionManager {
     private void retractSessionProposal(RtpSessionProposal rtpSessionProposal) {
         final Account account = rtpSessionProposal.account;
         toneManager.transition(RtpEndUserState.ENDED, rtpSessionProposal.media);
-        Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": retracting rtp session proposal with " + rtpSessionProposal.with);
+        Log.d(
+                Config.LOGTAG,
+                account.getJid().asBareJid()
+                        + ": retracting rtp session proposal with "
+                        + rtpSessionProposal.with);
         this.rtpSessionProposals.remove(rtpSessionProposal);
-        final MessagePacket messagePacket = mXmppConnectionService.getMessageGenerator().sessionRetract(rtpSessionProposal);
-        writeLogMissedOutgoing(account, rtpSessionProposal.with, rtpSessionProposal.sessionId, null, System.currentTimeMillis());
+        final MessagePacket messagePacket =
+                mXmppConnectionService.getMessageGenerator().sessionRetract(rtpSessionProposal);
+        writeLogMissedOutgoing(
+                account,
+                rtpSessionProposal.with,
+                rtpSessionProposal.sessionId,
+                null,
+                System.currentTimeMillis());
         mXmppConnectionService.sendMessagePacket(account, messagePacket);
     }
 
-    public String initializeRtpSession(final Account account, final Jid with, final Set<Media> media) {
+    public String initializeRtpSession(
+            final Account account, final Jid with, final Set<Media> media) {
         final AbstractJingleConnection.Id id = AbstractJingleConnection.Id.of(account, with);
-        final JingleRtpConnection rtpConnection = new JingleRtpConnection(this, id, account.getJid());
+        final JingleRtpConnection rtpConnection =
+                new JingleRtpConnection(this, id, account.getJid());
         rtpConnection.setProposedMedia(media);
         this.connections.put(id, rtpConnection);
         rtpConnection.sendSessionInitiate();
         return id.sessionId;
     }
 
-    public void proposeJingleRtpSession(final Account account, final Jid with, final Set<Media> media) {
+    public void proposeJingleRtpSession(
+            final Account account, final Jid with, final Set<Media> media) {
         synchronized (this.rtpSessionProposals) {
-            for (Map.Entry<RtpSessionProposal, DeviceDiscoveryState> entry : this.rtpSessionProposals.entrySet()) {
+            for (Map.Entry<RtpSessionProposal, DeviceDiscoveryState> entry :
+                    this.rtpSessionProposals.entrySet()) {
                 RtpSessionProposal proposal = entry.getKey();
                 if (proposal.account == account && with.asBareJid().equals(proposal.with)) {
                     final DeviceDiscoveryState preexistingState = entry.getValue();
-                    if (preexistingState != null && preexistingState != DeviceDiscoveryState.FAILED) {
+                    if (preexistingState != null
+                            && preexistingState != DeviceDiscoveryState.FAILED) {
                         final RtpEndUserState endUserState = preexistingState.toEndUserState();
                         toneManager.transition(endUserState, media);
                         mXmppConnectionService.notifyJingleRtpConnectionUpdate(
-                                account,
-                                with,
-                                proposal.sessionId,
-                                endUserState
-                        );
+                                account, with, proposal.sessionId, endUserState);
                         return;
                     }
                 }
             }
             if (isBusy()) {
                 if (hasMatchingRtpSession(account, with, media)) {
-                    Log.d(Config.LOGTAG, "ignoring request to propose jingle session because the other party already created one for us");
+                    Log.d(
+                            Config.LOGTAG,
+                            "ignoring request to propose jingle session because the other party already created one for us");
                     return;
                 }
-                throw new IllegalStateException("There is already a running RTP session. This should have been caught by the UI");
+                throw new IllegalStateException(
+                        "There is already a running RTP session. This should have been caught by the UI");
             }
-            final RtpSessionProposal proposal = RtpSessionProposal.of(account, with.asBareJid(), media);
+            final RtpSessionProposal proposal =
+                    RtpSessionProposal.of(account, with.asBareJid(), media);
             this.rtpSessionProposals.put(proposal, DeviceDiscoveryState.SEARCHING);
             mXmppConnectionService.notifyJingleRtpConnectionUpdate(
-                    account,
-                    proposal.with,
-                    proposal.sessionId,
-                    RtpEndUserState.FINDING_DEVICE
-            );
-            final MessagePacket messagePacket = mXmppConnectionService.getMessageGenerator().sessionProposal(proposal);
+                    account, proposal.with, proposal.sessionId, RtpEndUserState.FINDING_DEVICE);
+            final MessagePacket messagePacket =
+                    mXmppConnectionService.getMessageGenerator().sessionProposal(proposal);
             mXmppConnectionService.sendMessagePacket(account, messagePacket);
         }
     }
 
     public boolean hasMatchingProposal(final Account account, final Jid with) {
         synchronized (this.rtpSessionProposals) {
-            for (Map.Entry<RtpSessionProposal, DeviceDiscoveryState> entry : this.rtpSessionProposals.entrySet()) {
+            for (Map.Entry<RtpSessionProposal, DeviceDiscoveryState> entry :
+                    this.rtpSessionProposals.entrySet()) {
                 final RtpSessionProposal proposal = entry.getKey();
                 if (proposal.account == account && with.asBareJid().equals(proposal.with)) {
                     return true;
@@ -642,10 +796,12 @@ public class JingleConnectionManager extends AbstractConnectionManager {
         if (sid != null) {
             for (final AbstractJingleConnection connection : this.connections.values()) {
                 if (connection instanceof JingleFileTransferConnection) {
-                    final JingleFileTransferConnection fileTransfer = (JingleFileTransferConnection) connection;
+                    final JingleFileTransferConnection fileTransfer =
+                            (JingleFileTransferConnection) connection;
                     final JingleTransport transport = fileTransfer.getTransport();
                     if (transport instanceof JingleInBandTransport) {
-                        final JingleInBandTransport inBandTransport = (JingleInBandTransport) transport;
+                        final JingleInBandTransport inBandTransport =
+                                (JingleInBandTransport) transport;
                         if (inBandTransport.matches(account, sid)) {
                             inBandTransport.deliverPayload(packet, payload);
                         }
@@ -655,7 +811,8 @@ public class JingleConnectionManager extends AbstractConnectionManager {
             }
         }
         Log.d(Config.LOGTAG, "unable to deliver ibb packet: " + packet.toString());
-        account.getXmppConnection().sendIqPacket(packet.generateResponse(IqPacket.TYPE.ERROR), null);
+        account.getXmppConnection()
+                .sendIqPacket(packet.generateResponse(IqPacket.TYPE.ERROR), null);
     }
 
     public void notifyRebound(final Account account) {
@@ -668,8 +825,10 @@ public class JingleConnectionManager extends AbstractConnectionManager {
         }
     }
 
-    public WeakReference<JingleRtpConnection> findJingleRtpConnection(Account account, Jid with, String sessionId) {
-        final AbstractJingleConnection.Id id = AbstractJingleConnection.Id.of(account, with, sessionId);
+    public WeakReference<JingleRtpConnection> findJingleRtpConnection(
+            Account account, Jid with, String sessionId) {
+        final AbstractJingleConnection.Id id =
+                AbstractJingleConnection.Id.of(account, with, sessionId);
         final AbstractJingleConnection connection = connections.get(id);
         if (connection instanceof JingleRtpConnection) {
             return new WeakReference<>((JingleRtpConnection) connection);
@@ -679,34 +838,53 @@ public class JingleConnectionManager extends AbstractConnectionManager {
 
     private void resendSessionProposals(final Account account) {
         synchronized (this.rtpSessionProposals) {
-            for (final Map.Entry<RtpSessionProposal, DeviceDiscoveryState> entry : this.rtpSessionProposals.entrySet()) {
+            for (final Map.Entry<RtpSessionProposal, DeviceDiscoveryState> entry :
+                    this.rtpSessionProposals.entrySet()) {
                 final RtpSessionProposal proposal = entry.getKey();
-                if (entry.getValue() == DeviceDiscoveryState.SEARCHING && proposal.account == account) {
-                    Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": resending session proposal to " + proposal.with);
-                    final MessagePacket messagePacket = mXmppConnectionService.getMessageGenerator().sessionProposal(proposal);
+                if (entry.getValue() == DeviceDiscoveryState.SEARCHING
+                        && proposal.account == account) {
+                    Log.d(
+                            Config.LOGTAG,
+                            account.getJid().asBareJid()
+                                    + ": resending session proposal to "
+                                    + proposal.with);
+                    final MessagePacket messagePacket =
+                            mXmppConnectionService.getMessageGenerator().sessionProposal(proposal);
                     mXmppConnectionService.sendMessagePacket(account, messagePacket);
                 }
             }
         }
     }
 
-    public void updateProposedSessionDiscovered(Account account, Jid from, String sessionId, final DeviceDiscoveryState target) {
+    public void updateProposedSessionDiscovered(
+            Account account, Jid from, String sessionId, final DeviceDiscoveryState target) {
         synchronized (this.rtpSessionProposals) {
-            final RtpSessionProposal sessionProposal = getRtpSessionProposal(account, from.asBareJid(), sessionId);
-            final DeviceDiscoveryState currentState = sessionProposal == null ? null : rtpSessionProposals.get(sessionProposal);
+            final RtpSessionProposal sessionProposal =
+                    getRtpSessionProposal(account, from.asBareJid(), sessionId);
+            final DeviceDiscoveryState currentState =
+                    sessionProposal == null ? null : rtpSessionProposals.get(sessionProposal);
             if (currentState == null) {
                 Log.d(Config.LOGTAG, "unable to find session proposal for session id " + sessionId);
                 return;
             }
             if (currentState == DeviceDiscoveryState.DISCOVERED) {
-                Log.d(Config.LOGTAG, "session proposal already at discovered. not going to fall back");
+                Log.d(
+                        Config.LOGTAG,
+                        "session proposal already at discovered. not going to fall back");
                 return;
             }
             this.rtpSessionProposals.put(sessionProposal, target);
             final RtpEndUserState endUserState = target.toEndUserState();
             toneManager.transition(endUserState, sessionProposal.media);
-            mXmppConnectionService.notifyJingleRtpConnectionUpdate(account, sessionProposal.with, sessionProposal.sessionId, endUserState);
-            Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": flagging session " + sessionId + " as " + target);
+            mXmppConnectionService.notifyJingleRtpConnectionUpdate(
+                    account, sessionProposal.with, sessionProposal.sessionId, endUserState);
+            Log.d(
+                    Config.LOGTAG,
+                    account.getJid().asBareJid()
+                            + ": flagging session "
+                            + sessionId
+                            + " as "
+                            + target);
         }
     }
 
@@ -731,7 +909,8 @@ public class JingleConnectionManager extends AbstractConnectionManager {
     }
 
     public void failProceed(Account account, final Jid with, String sessionId) {
-        final AbstractJingleConnection.Id id = AbstractJingleConnection.Id.of(account, with, sessionId);
+        final AbstractJingleConnection.Id id =
+                AbstractJingleConnection.Id.of(account, with, sessionId);
         final AbstractJingleConnection existingJingleConnection = connections.get(id);
         if (existingJingleConnection instanceof JingleRtpConnection) {
             ((JingleRtpConnection) existingJingleConnection).deliverFailedProceed();
@@ -742,13 +921,17 @@ public class JingleConnectionManager extends AbstractConnectionManager {
         if (connections.containsValue(connection)) {
             return;
         }
-        final IllegalStateException e = new IllegalStateException("JingleConnection has not been registered with connection manager");
+        final IllegalStateException e =
+                new IllegalStateException(
+                        "JingleConnection has not been registered with connection manager");
         Log.e(Config.LOGTAG, "ensureConnectionIsRegistered() failed. Going to throw", e);
         throw e;
     }
 
-    void setTerminalSessionState(AbstractJingleConnection.Id id, final RtpEndUserState state, final Set<Media> media) {
-        this.terminatedSessions.put(PersistableSessionId.of(id), new TerminatedRtpSession(state, media));
+    void setTerminalSessionState(
+            AbstractJingleConnection.Id id, final RtpEndUserState state, final Set<Media> media) {
+        this.terminatedSessions.put(
+                PersistableSessionId.of(id), new TerminatedRtpSession(state, media));
     }
 
     public TerminatedRtpSession getTerminalSessionState(final Jid with, final String sessionId) {
@@ -773,8 +956,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
             if (this == o) return true;
             if (o == null || getClass() != o.getClass()) return false;
             PersistableSessionId that = (PersistableSessionId) o;
-            return Objects.equal(with, that.with) &&
-                    Objects.equal(sessionId, that.sessionId);
+            return Objects.equal(with, that.with) && Objects.equal(sessionId, that.sessionId);
         }
 
         @Override
@@ -794,7 +976,10 @@ public class JingleConnectionManager extends AbstractConnectionManager {
     }
 
     public enum DeviceDiscoveryState {
-        SEARCHING, SEARCHING_ACKNOWLEDGED, DISCOVERED, FAILED;
+        SEARCHING,
+        SEARCHING_ACKNOWLEDGED,
+        DISCOVERED,
+        FAILED;
 
         public RtpEndUserState toEndUserState() {
             switch (this) {
@@ -835,9 +1020,9 @@ public class JingleConnectionManager extends AbstractConnectionManager {
             if (this == o) return true;
             if (o == null || getClass() != o.getClass()) return false;
             RtpSessionProposal proposal = (RtpSessionProposal) o;
-            return Objects.equal(account.getJid(), proposal.account.getJid()) &&
-                    Objects.equal(with, proposal.with) &&
-                    Objects.equal(sessionId, proposal.sessionId);
+            return Objects.equal(account.getJid(), proposal.account.getJid())
+                    && Objects.equal(with, proposal.with)
+                    && Objects.equal(sessionId, proposal.sessionId);
         }
 
         @Override

src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java πŸ”—

@@ -33,6 +33,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.Queue;
 import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
@@ -61,91 +63,103 @@ import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription;
 import eu.siacs.conversations.xmpp.stanzas.IqPacket;
 import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
 
-public class JingleRtpConnection extends AbstractJingleConnection implements WebRTCWrapper.EventCallback {
+public class JingleRtpConnection extends AbstractJingleConnection
+        implements WebRTCWrapper.EventCallback {
 
-    public static final List<State> STATES_SHOWING_ONGOING_CALL = Arrays.asList(
-            State.PROCEED,
-            State.SESSION_INITIALIZED_PRE_APPROVED,
-            State.SESSION_ACCEPTED
-    );
+    public static final List<State> STATES_SHOWING_ONGOING_CALL =
+            Arrays.asList(
+                    State.PROCEED, State.SESSION_INITIALIZED_PRE_APPROVED, State.SESSION_ACCEPTED);
     private static final long BUSY_TIME_OUT = 30;
-    private static final List<State> TERMINATED = Arrays.asList(
-            State.ACCEPTED,
-            State.REJECTED,
-            State.REJECTED_RACED,
-            State.RETRACTED,
-            State.RETRACTED_RACED,
-            State.TERMINATED_SUCCESS,
-            State.TERMINATED_DECLINED_OR_BUSY,
-            State.TERMINATED_CONNECTIVITY_ERROR,
-            State.TERMINATED_CANCEL_OR_TIMEOUT,
-            State.TERMINATED_APPLICATION_FAILURE,
-            State.TERMINATED_SECURITY_ERROR
-    );
+    private static final List<State> TERMINATED =
+            Arrays.asList(
+                    State.ACCEPTED,
+                    State.REJECTED,
+                    State.REJECTED_RACED,
+                    State.RETRACTED,
+                    State.RETRACTED_RACED,
+                    State.TERMINATED_SUCCESS,
+                    State.TERMINATED_DECLINED_OR_BUSY,
+                    State.TERMINATED_CONNECTIVITY_ERROR,
+                    State.TERMINATED_CANCEL_OR_TIMEOUT,
+                    State.TERMINATED_APPLICATION_FAILURE,
+                    State.TERMINATED_SECURITY_ERROR);
 
     private static final Map<State, Collection<State>> VALID_TRANSITIONS;
 
     static {
-        final ImmutableMap.Builder<State, Collection<State>> transitionBuilder = new ImmutableMap.Builder<>();
-        transitionBuilder.put(State.NULL, ImmutableList.of(
+        final ImmutableMap.Builder<State, Collection<State>> transitionBuilder =
+                new ImmutableMap.Builder<>();
+        transitionBuilder.put(
+                State.NULL,
+                ImmutableList.of(
+                        State.PROPOSED,
+                        State.SESSION_INITIALIZED,
+                        State.TERMINATED_APPLICATION_FAILURE,
+                        State.TERMINATED_SECURITY_ERROR));
+        transitionBuilder.put(
                 State.PROPOSED,
-                State.SESSION_INITIALIZED,
-                State.TERMINATED_APPLICATION_FAILURE,
-                State.TERMINATED_SECURITY_ERROR
-        ));
-        transitionBuilder.put(State.PROPOSED, ImmutableList.of(
-                State.ACCEPTED,
+                ImmutableList.of(
+                        State.ACCEPTED,
+                        State.PROCEED,
+                        State.REJECTED,
+                        State.RETRACTED,
+                        State.TERMINATED_APPLICATION_FAILURE,
+                        State.TERMINATED_SECURITY_ERROR,
+                        State.TERMINATED_CONNECTIVITY_ERROR // only used when the xmpp connection
+                        // rebinds
+                        ));
+        transitionBuilder.put(
                 State.PROCEED,
-                State.REJECTED,
-                State.RETRACTED,
-                State.TERMINATED_APPLICATION_FAILURE,
-                State.TERMINATED_SECURITY_ERROR,
-                State.TERMINATED_CONNECTIVITY_ERROR //only used when the xmpp connection rebinds
-        ));
-        transitionBuilder.put(State.PROCEED, ImmutableList.of(
-                State.REJECTED_RACED,
-                State.RETRACTED_RACED,
+                ImmutableList.of(
+                        State.REJECTED_RACED,
+                        State.RETRACTED_RACED,
+                        State.SESSION_INITIALIZED_PRE_APPROVED,
+                        State.TERMINATED_SUCCESS,
+                        State.TERMINATED_APPLICATION_FAILURE,
+                        State.TERMINATED_SECURITY_ERROR,
+                        State.TERMINATED_CONNECTIVITY_ERROR // at this state used for error
+                        // bounces of the proceed message
+                        ));
+        transitionBuilder.put(
+                State.SESSION_INITIALIZED,
+                ImmutableList.of(
+                        State.SESSION_ACCEPTED,
+                        State.TERMINATED_SUCCESS,
+                        State.TERMINATED_DECLINED_OR_BUSY,
+                        State.TERMINATED_CONNECTIVITY_ERROR, // at this state used for IQ errors
+                        // and IQ timeouts
+                        State.TERMINATED_CANCEL_OR_TIMEOUT,
+                        State.TERMINATED_APPLICATION_FAILURE,
+                        State.TERMINATED_SECURITY_ERROR));
+        transitionBuilder.put(
                 State.SESSION_INITIALIZED_PRE_APPROVED,
-                State.TERMINATED_SUCCESS,
-                State.TERMINATED_APPLICATION_FAILURE,
-                State.TERMINATED_SECURITY_ERROR,
-                State.TERMINATED_CONNECTIVITY_ERROR //at this state used for error bounces of the proceed message
-        ));
-        transitionBuilder.put(State.SESSION_INITIALIZED, ImmutableList.of(
+                ImmutableList.of(
+                        State.SESSION_ACCEPTED,
+                        State.TERMINATED_SUCCESS,
+                        State.TERMINATED_DECLINED_OR_BUSY,
+                        State.TERMINATED_CONNECTIVITY_ERROR, // at this state used for IQ errors
+                        // and IQ timeouts
+                        State.TERMINATED_CANCEL_OR_TIMEOUT,
+                        State.TERMINATED_APPLICATION_FAILURE,
+                        State.TERMINATED_SECURITY_ERROR));
+        transitionBuilder.put(
                 State.SESSION_ACCEPTED,
-                State.TERMINATED_SUCCESS,
-                State.TERMINATED_DECLINED_OR_BUSY,
-                State.TERMINATED_CONNECTIVITY_ERROR,  //at this state used for IQ errors and IQ timeouts
-                State.TERMINATED_CANCEL_OR_TIMEOUT,
-                State.TERMINATED_APPLICATION_FAILURE,
-                State.TERMINATED_SECURITY_ERROR
-        ));
-        transitionBuilder.put(State.SESSION_INITIALIZED_PRE_APPROVED, ImmutableList.of(
-                State.SESSION_ACCEPTED,
-                State.TERMINATED_SUCCESS,
-                State.TERMINATED_DECLINED_OR_BUSY,
-                State.TERMINATED_CONNECTIVITY_ERROR,  //at this state used for IQ errors and IQ timeouts
-                State.TERMINATED_CANCEL_OR_TIMEOUT,
-                State.TERMINATED_APPLICATION_FAILURE,
-                State.TERMINATED_SECURITY_ERROR
-        ));
-        transitionBuilder.put(State.SESSION_ACCEPTED, ImmutableList.of(
-                State.TERMINATED_SUCCESS,
-                State.TERMINATED_DECLINED_OR_BUSY,
-                State.TERMINATED_CONNECTIVITY_ERROR,
-                State.TERMINATED_CANCEL_OR_TIMEOUT,
-                State.TERMINATED_APPLICATION_FAILURE,
-                State.TERMINATED_SECURITY_ERROR
-        ));
+                ImmutableList.of(
+                        State.TERMINATED_SUCCESS,
+                        State.TERMINATED_DECLINED_OR_BUSY,
+                        State.TERMINATED_CONNECTIVITY_ERROR,
+                        State.TERMINATED_CANCEL_OR_TIMEOUT,
+                        State.TERMINATED_APPLICATION_FAILURE,
+                        State.TERMINATED_SECURITY_ERROR));
         VALID_TRANSITIONS = transitionBuilder.build();
     }
 
     private final WebRTCWrapper webRTCWrapper = new WebRTCWrapper(this);
-    private final Queue<Map.Entry<String, RtpContentMap.DescriptionTransport>> pendingIceCandidates = new LinkedList<>();
+    private final Queue<Map.Entry<String, RtpContentMap.DescriptionTransport>>
+            pendingIceCandidates = new LinkedList<>();
     private final OmemoVerification omemoVerification = new OmemoVerification();
     private final Message message;
     private State state = State.NULL;
-    private StateTransitionException stateTransitionException;
     private Set<Media> proposedMedia;
     private RtpContentMap initiatorRtpContentMap;
     private RtpContentMap responderRtpContentMap;
@@ -156,18 +170,16 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
 
     JingleRtpConnection(JingleConnectionManager jingleConnectionManager, Id id, Jid initiator) {
         super(jingleConnectionManager, id, initiator);
-        final Conversation conversation = jingleConnectionManager.getXmppConnectionService().findOrCreateConversation(
-                id.account,
-                id.with.asBareJid(),
-                false,
-                false
-        );
-        this.message = new Message(
-                conversation,
-                isInitiator() ? Message.STATUS_SEND : Message.STATUS_RECEIVED,
-                Message.TYPE_RTP_SESSION,
-                id.sessionId
-        );
+        final Conversation conversation =
+                jingleConnectionManager
+                        .getXmppConnectionService()
+                        .findOrCreateConversation(id.account, id.with.asBareJid(), false, false);
+        this.message =
+                new Message(
+                        conversation,
+                        isInitiator() ? Message.STATUS_SEND : Message.STATUS_RECEIVED,
+                        Message.TYPE_RTP_SESSION,
+                        id.sessionId);
     }
 
     private static State reasonToState(Reason reason) {
@@ -208,7 +220,11 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
                 break;
             default:
                 respondOk(jinglePacket);
-                Log.d(Config.LOGTAG, String.format("%s: received unhandled jingle action %s", id.account.getJid().asBareJid(), jinglePacket.getAction()));
+                Log.d(
+                        Config.LOGTAG,
+                        String.format(
+                                "%s: received unhandled jingle action %s",
+                                id.account.getJid().asBareJid(), jinglePacket.getAction()));
                 break;
         }
     }
@@ -222,8 +238,12 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
         if (!isInitiator() && isInState(State.PROPOSED, State.SESSION_INITIALIZED)) {
             xmppConnectionService.getNotificationService().cancelIncomingCallNotification();
         }
-        if (isInState(State.SESSION_INITIALIZED, State.SESSION_INITIALIZED_PRE_APPROVED, State.SESSION_ACCEPTED)) {
-            //we might have already changed resources (full jid) at this point; so this might not even reach the other party
+        if (isInState(
+                State.SESSION_INITIALIZED,
+                State.SESSION_INITIALIZED_PRE_APPROVED,
+                State.SESSION_ACCEPTED)) {
+            // we might have already changed resources (full jid) at this point; so this might not
+            // even reach the other party
             sendSessionTerminate(Reason.CONNECTIVITY_ERROR);
         } else {
             transitionOrThrow(State.TERMINATED_CONNECTIVITY_ERROR);
@@ -235,9 +255,21 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
         respondOk(jinglePacket);
         final JinglePacket.ReasonWrapper wrapper = jinglePacket.getReason();
         final State previous = this.state;
-        Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": received session terminate reason=" + wrapper.reason + "(" + Strings.nullToEmpty(wrapper.text) + ") while in state " + previous);
+        Log.d(
+                Config.LOGTAG,
+                id.account.getJid().asBareJid()
+                        + ": received session terminate reason="
+                        + wrapper.reason
+                        + "("
+                        + Strings.nullToEmpty(wrapper.text)
+                        + ") while in state "
+                        + previous);
         if (TERMINATED.contains(previous)) {
-            Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": ignoring session terminate because already in " + previous);
+            Log.d(
+                    Config.LOGTAG,
+                    id.account.getJid().asBareJid()
+                            + ": ignoring session terminate because already in "
+                            + previous);
             return;
         }
         webRTCWrapper.close();
@@ -251,13 +283,23 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
     }
 
     private void receiveTransportInfo(final JinglePacket jinglePacket) {
-        //Due to the asynchronicity of processing session-init we might move from NULL|PROCEED to INITIALIZED only after transport-info has been received
-        if (isInState(State.NULL, State.PROCEED, State.SESSION_INITIALIZED, State.SESSION_INITIALIZED_PRE_APPROVED, State.SESSION_ACCEPTED)) {
+        // Due to the asynchronicity of processing session-init we might move from NULL|PROCEED to
+        // INITIALIZED only after transport-info has been received
+        if (isInState(
+                State.NULL,
+                State.PROCEED,
+                State.SESSION_INITIALIZED,
+                State.SESSION_INITIALIZED_PRE_APPROVED,
+                State.SESSION_ACCEPTED)) {
             final RtpContentMap contentMap;
             try {
                 contentMap = RtpContentMap.of(jinglePacket);
             } catch (final IllegalArgumentException | NullPointerException e) {
-                Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": improperly formatted contents; ignoring", e);
+                Log.d(
+                        Config.LOGTAG,
+                        id.account.getJid().asBareJid()
+                                + ": improperly formatted contents; ignoring",
+                        e);
                 respondOk(jinglePacket);
                 return;
             }
@@ -265,18 +307,27 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
         } else {
             if (isTerminated()) {
                 respondOk(jinglePacket);
-                Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": ignoring out-of-order transport info; we where already terminated");
+                Log.d(
+                        Config.LOGTAG,
+                        id.account.getJid().asBareJid()
+                                + ": ignoring out-of-order transport info; we where already terminated");
             } else {
-                Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": received transport info while in state=" + this.state);
+                Log.d(
+                        Config.LOGTAG,
+                        id.account.getJid().asBareJid()
+                                + ": received transport info while in state="
+                                + this.state);
                 terminateWithOutOfOrder(jinglePacket);
             }
         }
     }
 
-    private void receiveTransportInfo(final JinglePacket jinglePacket, final RtpContentMap contentMap) {
-        final Set<Map.Entry<String, RtpContentMap.DescriptionTransport>> candidates = contentMap.contents.entrySet();
+    private void receiveTransportInfo(
+            final JinglePacket jinglePacket, final RtpContentMap contentMap) {
+        final Set<Map.Entry<String, RtpContentMap.DescriptionTransport>> candidates =
+                contentMap.contents.entrySet();
         if (this.state == State.SESSION_ACCEPTED) {
-            //zero candidates + modified credentials are an ICE restart offer
+            // zero candidates + modified credentials are an ICE restart offer
             if (checkForIceRestart(jinglePacket, contentMap)) {
                 return;
             }
@@ -284,7 +335,10 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
             try {
                 processCandidates(candidates);
             } catch (final WebRTCWrapper.PeerConnectionNotInitialized e) {
-                Log.w(Config.LOGTAG, id.account.getJid().asBareJid() + ": PeerConnection was not initialized when processing transport info. this usually indicates a race condition that can be ignored");
+                Log.w(
+                        Config.LOGTAG,
+                        id.account.getJid().asBareJid()
+                                + ": PeerConnection was not initialized when processing transport info. this usually indicates a race condition that can be ignored");
             }
         } else {
             respondOk(jinglePacket);
@@ -292,7 +346,8 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
         }
     }
 
-    private boolean checkForIceRestart(final JinglePacket jinglePacket, final RtpContentMap rtpContentMap) {
+    private boolean checkForIceRestart(
+            final JinglePacket jinglePacket, final RtpContentMap rtpContentMap) {
         final RtpContentMap existing = getRemoteContentMap();
         final IceUdpTransportInfo.Credentials existingCredentials;
         final IceUdpTransportInfo.Credentials newCredentials;
@@ -306,7 +361,8 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
         if (existingCredentials.equals(newCredentials)) {
             return false;
         }
-        //TODO an alternative approach is to check if we already got an iq result to our ICE-restart
+        // TODO an alternative approach is to check if we already got an iq result to our
+        // ICE-restart
         // and if that's the case we are seeing an answer.
         // This might be more spec compliant but also more error prone potentially
         final boolean isOffer = rtpContentMap.emptyCandidates();
@@ -314,10 +370,17 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
         try {
             if (isOffer) {
                 Log.d(Config.LOGTAG, "received offer to restart ICE " + newCredentials);
-                restartContentMap = existing.modifiedCredentials(newCredentials, IceUdpTransportInfo.Setup.ACTPASS);
+                restartContentMap =
+                        existing.modifiedCredentials(
+                                newCredentials, IceUdpTransportInfo.Setup.ACTPASS);
             } else {
                 final IceUdpTransportInfo.Setup setup = getPeerDtlsSetup();
-                Log.d(Config.LOGTAG, "received confirmation of ICE restart" + newCredentials + " peer_setup=" + setup);
+                Log.d(
+                        Config.LOGTAG,
+                        "received confirmation of ICE restart"
+                                + newCredentials
+                                + " peer_setup="
+                                + setup);
                 // DTLS setup attribute needs to be rewritten to reflect current peer state
                 // https://groups.google.com/g/discuss-webrtc/c/DfpIMwvUfeM
                 restartContentMap = existing.modifiedCredentials(newCredentials, setup);
@@ -333,7 +396,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
             respondOk(jinglePacket);
             final Throwable rootCause = Throwables.getRootCause(exception);
             if (rootCause instanceof WebRTCWrapper.PeerConnectionNotInitialized) {
-                //If this happens a termination is already in progress
+                // If this happens a termination is already in progress
                 Log.d(Config.LOGTAG, "ignoring PeerConnectionNotInitialized on ICE restart");
                 return true;
             }
@@ -359,13 +422,22 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
         this.peerDtlsSetup = setup;
     }
 
-    private boolean applyIceRestart(final JinglePacket jinglePacket, final RtpContentMap restartContentMap, final boolean isOffer) throws ExecutionException, InterruptedException {
+    private boolean applyIceRestart(
+            final JinglePacket jinglePacket,
+            final RtpContentMap restartContentMap,
+            final boolean isOffer)
+            throws ExecutionException, InterruptedException {
         final SessionDescription sessionDescription = SessionDescription.of(restartContentMap);
-        final org.webrtc.SessionDescription.Type type = isOffer ? org.webrtc.SessionDescription.Type.OFFER : org.webrtc.SessionDescription.Type.ANSWER;
-        org.webrtc.SessionDescription sdp = new org.webrtc.SessionDescription(type, sessionDescription.toString());
+        final org.webrtc.SessionDescription.Type type =
+                isOffer
+                        ? org.webrtc.SessionDescription.Type.OFFER
+                        : org.webrtc.SessionDescription.Type.ANSWER;
+        org.webrtc.SessionDescription sdp =
+                new org.webrtc.SessionDescription(type, sessionDescription.toString());
         if (isOffer && webRTCWrapper.getSignalingState() != PeerConnection.SignalingState.STABLE) {
             if (isInitiator()) {
-                //We ignore the offer and respond with tie-break. This will clause the responder not to apply the content map
+                // We ignore the offer and respond with tie-break. This will clause the responder
+                // not to apply the content map
                 return false;
             }
         }
@@ -375,7 +447,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
             webRTCWrapper.setIsReadyToReceiveIceCandidates(false);
             final SessionDescription localSessionDescription = setLocalSessionDescription();
             setLocalContentMap(RtpContentMap.of(localSessionDescription));
-            //We need to respond OK before sending any candidates
+            // We need to respond OK before sending any candidates
             respondOk(jinglePacket);
             webRTCWrapper.setIsReadyToReceiveIceCandidates(true);
         } else {
@@ -384,32 +456,40 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
         return true;
     }
 
-    private void processCandidates(final Set<Map.Entry<String, RtpContentMap.DescriptionTransport>> contents) {
+    private void processCandidates(
+            final Set<Map.Entry<String, RtpContentMap.DescriptionTransport>> contents) {
         for (final Map.Entry<String, RtpContentMap.DescriptionTransport> content : contents) {
             processCandidate(content);
         }
     }
 
-    private void processCandidate(final Map.Entry<String, RtpContentMap.DescriptionTransport> content) {
+    private void processCandidate(
+            final Map.Entry<String, RtpContentMap.DescriptionTransport> content) {
         final RtpContentMap rtpContentMap = getRemoteContentMap();
         final List<String> indices = toIdentificationTags(rtpContentMap);
-        final String sdpMid = content.getKey(); //aka content name
+        final String sdpMid = content.getKey(); // aka content name
         final IceUdpTransportInfo transport = content.getValue().transport;
         final IceUdpTransportInfo.Credentials credentials = transport.getCredentials();
 
-        //TODO check that credentials remained the same
+        // TODO check that credentials remained the same
 
         for (final IceUdpTransportInfo.Candidate candidate : transport.getCandidates()) {
             final String sdp;
             try {
                 sdp = candidate.toSdpAttribute(credentials.ufrag);
             } catch (final IllegalArgumentException e) {
-                Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": ignoring invalid ICE candidate " + e.getMessage());
+                Log.d(
+                        Config.LOGTAG,
+                        id.account.getJid().asBareJid()
+                                + ": ignoring invalid ICE candidate "
+                                + e.getMessage());
                 continue;
             }
             final int mLineIndex = indices.indexOf(sdpMid);
             if (mLineIndex < 0) {
-                Log.w(Config.LOGTAG, "mLineIndex not found for " + sdpMid + ". available indices " + indices);
+                Log.w(
+                        Config.LOGTAG,
+                        "mLineIndex not found for " + sdpMid + ". available indices " + indices);
             }
             final IceCandidate iceCandidate = new IceCandidate(sdpMid, mLineIndex, sdp);
             Log.d(Config.LOGTAG, "received candidate: " + iceCandidate);
@@ -423,14 +503,21 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
 
     private List<String> toIdentificationTags(final RtpContentMap rtpContentMap) {
         final Group originalGroup = rtpContentMap.group;
-        final List<String> identificationTags = originalGroup == null ? rtpContentMap.getNames() : originalGroup.getIdentificationTags();
+        final List<String> identificationTags =
+                originalGroup == null
+                        ? rtpContentMap.getNames()
+                        : originalGroup.getIdentificationTags();
         if (identificationTags.size() == 0) {
-            Log.w(Config.LOGTAG, id.account.getJid().asBareJid() + ": no identification tags found in initial offer. we won't be able to calculate mLineIndices");
+            Log.w(
+                    Config.LOGTAG,
+                    id.account.getJid().asBareJid()
+                            + ": no identification tags found in initial offer. we won't be able to calculate mLineIndices");
         }
         return identificationTags;
     }
 
-    private ListenableFuture<RtpContentMap> receiveRtpContentMap(final JinglePacket jinglePacket, final boolean expectVerification) {
+    private ListenableFuture<RtpContentMap> receiveRtpContentMap(
+            final JinglePacket jinglePacket, final boolean expectVerification) {
         final RtpContentMap receivedContentMap;
         try {
             receivedContentMap = RtpContentMap.of(jinglePacket);
@@ -438,17 +525,26 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
             return Futures.immediateFailedFuture(e);
         }
         if (receivedContentMap instanceof OmemoVerifiedRtpContentMap) {
-            final ListenableFuture<AxolotlService.OmemoVerifiedPayload<RtpContentMap>> future = id.account.getAxolotlService().decrypt((OmemoVerifiedRtpContentMap) receivedContentMap, id.with);
-            return Futures.transform(future, omemoVerifiedPayload -> {
-                //TODO test if an exception here triggers a correct abort
-                omemoVerification.setOrEnsureEqual(omemoVerifiedPayload);
-                Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": received verifiable DTLS fingerprint via " + omemoVerification);
-                return omemoVerifiedPayload.getPayload();
-            }, MoreExecutors.directExecutor());
+            final ListenableFuture<AxolotlService.OmemoVerifiedPayload<RtpContentMap>> future =
+                    id.account
+                            .getAxolotlService()
+                            .decrypt((OmemoVerifiedRtpContentMap) receivedContentMap, id.with);
+            return Futures.transform(
+                    future,
+                    omemoVerifiedPayload -> {
+                        // TODO test if an exception here triggers a correct abort
+                        omemoVerification.setOrEnsureEqual(omemoVerifiedPayload);
+                        Log.d(
+                                Config.LOGTAG,
+                                id.account.getJid().asBareJid()
+                                        + ": received verifiable DTLS fingerprint via "
+                                        + omemoVerification);
+                        return omemoVerifiedPayload.getPayload();
+                    },
+                    MoreExecutors.directExecutor());
         } else if (Config.REQUIRE_RTP_VERIFICATION || expectVerification) {
             return Futures.immediateFailedFuture(
-                    new SecurityException("DTLS fingerprint was unexpectedly not verifiable")
-            );
+                    new SecurityException("DTLS fingerprint was unexpectedly not verifiable"));
         } else {
             return Futures.immediateFuture(receivedContentMap);
         }
@@ -456,13 +552,17 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
 
     private void receiveSessionInitiate(final JinglePacket jinglePacket) {
         if (isInitiator()) {
-            Log.d(Config.LOGTAG, String.format("%s: received session-initiate even though we were initiating", id.account.getJid().asBareJid()));
+            Log.d(
+                    Config.LOGTAG,
+                    String.format(
+                            "%s: received session-initiate even though we were initiating",
+                            id.account.getJid().asBareJid()));
             if (isTerminated()) {
-                Log.d(Config.LOGTAG, String.format(
-                        "%s: got a reason to terminate with out-of-order. but already in state %s",
-                        id.account.getJid().asBareJid(),
-                        getState()
-                ));
+                Log.d(
+                        Config.LOGTAG,
+                        String.format(
+                                "%s: got a reason to terminate with out-of-order. but already in state %s",
+                                id.account.getJid().asBareJid(), getState()));
                 respondWithOutOfOrder(jinglePacket);
             } else {
                 terminateWithOutOfOrder(jinglePacket);
@@ -470,43 +570,51 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
             return;
         }
         final ListenableFuture<RtpContentMap> future = receiveRtpContentMap(jinglePacket, false);
-        Futures.addCallback(future, new FutureCallback<RtpContentMap>() {
-            @Override
-            public void onSuccess(@Nullable RtpContentMap rtpContentMap) {
-                receiveSessionInitiate(jinglePacket, rtpContentMap);
-            }
+        Futures.addCallback(
+                future,
+                new FutureCallback<RtpContentMap>() {
+                    @Override
+                    public void onSuccess(@Nullable RtpContentMap rtpContentMap) {
+                        receiveSessionInitiate(jinglePacket, rtpContentMap);
+                    }
 
-            @Override
-            public void onFailure(@NonNull final Throwable throwable) {
-                respondOk(jinglePacket);
-                sendSessionTerminate(Reason.ofThrowable(throwable), throwable.getMessage());
-            }
-        }, MoreExecutors.directExecutor());
+                    @Override
+                    public void onFailure(@NonNull final Throwable throwable) {
+                        respondOk(jinglePacket);
+                        sendSessionTerminate(Reason.ofThrowable(throwable), throwable.getMessage());
+                    }
+                },
+                MoreExecutors.directExecutor());
     }
 
-    private void receiveSessionInitiate(final JinglePacket jinglePacket, final RtpContentMap contentMap) {
+    private void receiveSessionInitiate(
+            final JinglePacket jinglePacket, final RtpContentMap contentMap) {
         try {
             contentMap.requireContentDescriptions();
             contentMap.requireDTLSFingerprint(true);
         } catch (final RuntimeException e) {
-            Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": improperly formatted contents", Throwables.getRootCause(e));
+            Log.d(
+                    Config.LOGTAG,
+                    id.account.getJid().asBareJid() + ": improperly formatted contents",
+                    Throwables.getRootCause(e));
             respondOk(jinglePacket);
             sendSessionTerminate(Reason.of(e), e.getMessage());
             return;
         }
-        Log.d(Config.LOGTAG, "processing session-init with " + contentMap.contents.size() + " contents");
+        Log.d(
+                Config.LOGTAG,
+                "processing session-init with " + contentMap.contents.size() + " contents");
         final State target;
         if (this.state == State.PROCEED) {
             Preconditions.checkState(
                     proposedMedia != null && proposedMedia.size() > 0,
-                    "proposed media must be set when processing pre-approved session-initiate"
-            );
+                    "proposed media must be set when processing pre-approved session-initiate");
             if (!this.proposedMedia.equals(contentMap.getMedia())) {
-                sendSessionTerminate(Reason.SECURITY_ERROR, String.format(
-                        "Your session proposal (Jingle Message Initiation) included media %s but your session-initiate was %s",
-                        this.proposedMedia,
-                        contentMap.getMedia()
-                ));
+                sendSessionTerminate(
+                        Reason.SECURITY_ERROR,
+                        String.format(
+                                "Your session proposal (Jingle Message Initiation) included media %s but your session-initiate was %s",
+                                this.proposedMedia, contentMap.getMedia()));
                 return;
             }
             target = State.SESSION_INITIALIZED_PRE_APPROVED;
@@ -517,67 +625,100 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
             respondOk(jinglePacket);
             pendingIceCandidates.addAll(contentMap.contents.entrySet());
             if (target == State.SESSION_INITIALIZED_PRE_APPROVED) {
-                Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": automatically accepting session-initiate");
+                Log.d(
+                        Config.LOGTAG,
+                        id.account.getJid().asBareJid()
+                                + ": automatically accepting session-initiate");
                 sendSessionAccept();
             } else {
-                Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": received not pre-approved session-initiate. start ringing");
+                Log.d(
+                        Config.LOGTAG,
+                        id.account.getJid().asBareJid()
+                                + ": received not pre-approved session-initiate. start ringing");
                 startRinging();
             }
         } else {
-            Log.d(Config.LOGTAG, String.format("%s: received session-initiate while in state %s", id.account.getJid().asBareJid(), state));
+            Log.d(
+                    Config.LOGTAG,
+                    String.format(
+                            "%s: received session-initiate while in state %s",
+                            id.account.getJid().asBareJid(), state));
             terminateWithOutOfOrder(jinglePacket);
         }
     }
 
     private void receiveSessionAccept(final JinglePacket jinglePacket) {
         if (!isInitiator()) {
-            Log.d(Config.LOGTAG, String.format("%s: received session-accept even though we were responding", id.account.getJid().asBareJid()));
+            Log.d(
+                    Config.LOGTAG,
+                    String.format(
+                            "%s: received session-accept even though we were responding",
+                            id.account.getJid().asBareJid()));
             terminateWithOutOfOrder(jinglePacket);
             return;
         }
-        final ListenableFuture<RtpContentMap> future = receiveRtpContentMap(jinglePacket, this.omemoVerification.hasFingerprint());
-        Futures.addCallback(future, new FutureCallback<RtpContentMap>() {
-            @Override
-            public void onSuccess(@Nullable RtpContentMap rtpContentMap) {
-                receiveSessionAccept(jinglePacket, rtpContentMap);
-            }
+        final ListenableFuture<RtpContentMap> future =
+                receiveRtpContentMap(jinglePacket, this.omemoVerification.hasFingerprint());
+        Futures.addCallback(
+                future,
+                new FutureCallback<RtpContentMap>() {
+                    @Override
+                    public void onSuccess(@Nullable RtpContentMap rtpContentMap) {
+                        receiveSessionAccept(jinglePacket, rtpContentMap);
+                    }
 
-            @Override
-            public void onFailure(@NonNull final Throwable throwable) {
-                respondOk(jinglePacket);
-                Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": improperly formatted contents in session-accept", throwable);
-                webRTCWrapper.close();
-                sendSessionTerminate(Reason.ofThrowable(throwable), throwable.getMessage());
-            }
-        }, MoreExecutors.directExecutor());
+                    @Override
+                    public void onFailure(@NonNull final Throwable throwable) {
+                        respondOk(jinglePacket);
+                        Log.d(
+                                Config.LOGTAG,
+                                id.account.getJid().asBareJid()
+                                        + ": improperly formatted contents in session-accept",
+                                throwable);
+                        webRTCWrapper.close();
+                        sendSessionTerminate(Reason.ofThrowable(throwable), throwable.getMessage());
+                    }
+                },
+                MoreExecutors.directExecutor());
     }
 
-    private void receiveSessionAccept(final JinglePacket jinglePacket, final RtpContentMap contentMap) {
+    private void receiveSessionAccept(
+            final JinglePacket jinglePacket, final RtpContentMap contentMap) {
         try {
             contentMap.requireContentDescriptions();
             contentMap.requireDTLSFingerprint();
         } catch (final RuntimeException e) {
             respondOk(jinglePacket);
-            Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": improperly formatted contents in session-accept", e);
+            Log.d(
+                    Config.LOGTAG,
+                    id.account.getJid().asBareJid()
+                            + ": improperly formatted contents in session-accept",
+                    e);
             webRTCWrapper.close();
             sendSessionTerminate(Reason.of(e), e.getMessage());
             return;
         }
         final Set<Media> initiatorMedia = this.initiatorRtpContentMap.getMedia();
         if (!initiatorMedia.equals(contentMap.getMedia())) {
-            sendSessionTerminate(Reason.SECURITY_ERROR, String.format(
-                    "Your session-included included media %s but our session-initiate was %s",
-                    this.proposedMedia,
-                    contentMap.getMedia()
-            ));
+            sendSessionTerminate(
+                    Reason.SECURITY_ERROR,
+                    String.format(
+                            "Your session-included included media %s but our session-initiate was %s",
+                            this.proposedMedia, contentMap.getMedia()));
             return;
         }
-        Log.d(Config.LOGTAG, "processing session-accept with " + contentMap.contents.size() + " contents");
+        Log.d(
+                Config.LOGTAG,
+                "processing session-accept with " + contentMap.contents.size() + " contents");
         if (transition(State.SESSION_ACCEPTED)) {
             respondOk(jinglePacket);
             receiveSessionAccept(contentMap);
         } else {
-            Log.d(Config.LOGTAG, String.format("%s: received session-accept while in state %s", id.account.getJid().asBareJid(), state));
+            Log.d(
+                    Config.LOGTAG,
+                    String.format(
+                            "%s: received session-accept while in state %s",
+                            id.account.getJid().asBareJid(), state));
             respondOk(jinglePacket);
         }
     }
@@ -589,21 +730,29 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
         try {
             sessionDescription = SessionDescription.of(contentMap);
         } catch (final IllegalArgumentException | NullPointerException e) {
-            Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": unable convert offer from session-accept to SDP", e);
+            Log.d(
+                    Config.LOGTAG,
+                    id.account.getJid().asBareJid()
+                            + ": unable convert offer from session-accept to SDP",
+                    e);
             webRTCWrapper.close();
             sendSessionTerminate(Reason.FAILED_APPLICATION, e.getMessage());
             return;
         }
-        final org.webrtc.SessionDescription answer = new org.webrtc.SessionDescription(
-                org.webrtc.SessionDescription.Type.ANSWER,
-                sessionDescription.toString()
-        );
+        final org.webrtc.SessionDescription answer =
+                new org.webrtc.SessionDescription(
+                        org.webrtc.SessionDescription.Type.ANSWER, sessionDescription.toString());
         try {
             this.webRTCWrapper.setRemoteDescription(answer).get();
         } catch (final Exception e) {
-            Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": unable to set remote description after receiving session-accept", Throwables.getRootCause(e));
+            Log.d(
+                    Config.LOGTAG,
+                    id.account.getJid().asBareJid()
+                            + ": unable to set remote description after receiving session-accept",
+                    Throwables.getRootCause(e));
             webRTCWrapper.close();
-            sendSessionTerminate(Reason.FAILED_APPLICATION, Throwables.getRootCause(e).getMessage());
+            sendSessionTerminate(
+                    Reason.FAILED_APPLICATION, Throwables.getRootCause(e).getMessage());
             return;
         }
         processCandidates(contentMap.contents.entrySet());
@@ -618,7 +767,11 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
         try {
             offer = SessionDescription.of(rtpContentMap);
         } catch (final IllegalArgumentException | NullPointerException e) {
-            Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": unable convert offer from session-initiate to SDP", e);
+            Log.d(
+                    Config.LOGTAG,
+                    id.account.getJid().asBareJid()
+                            + ": unable convert offer from session-initiate to SDP",
+                    e);
             webRTCWrapper.close();
             sendSessionTerminate(Reason.FAILED_APPLICATION, e.getMessage());
             return;
@@ -630,9 +783,15 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
         discoverIceServers(iceServers -> sendSessionAccept(media, offer, iceServers));
     }
 
-    private synchronized void sendSessionAccept(final Set<Media> media, final SessionDescription offer, final List<PeerConnection.IceServer> iceServers) {
+    private synchronized void sendSessionAccept(
+            final Set<Media> media,
+            final SessionDescription offer,
+            final List<PeerConnection.IceServer> iceServers) {
         if (isTerminated()) {
-            Log.w(Config.LOGTAG, id.account.getJid().asBareJid() + ": ICE servers got discovered when session was already terminated. nothing to do.");
+            Log.w(
+                    Config.LOGTAG,
+                    id.account.getJid().asBareJid()
+                            + ": ICE servers got discovered when session was already terminated. nothing to do.");
             return;
         }
         try {
@@ -643,14 +802,14 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
             sendSessionTerminate(Reason.FAILED_APPLICATION);
             return;
         }
-        final org.webrtc.SessionDescription sdp = new org.webrtc.SessionDescription(
-                org.webrtc.SessionDescription.Type.OFFER,
-                offer.toString()
-        );
+        final org.webrtc.SessionDescription sdp =
+                new org.webrtc.SessionDescription(
+                        org.webrtc.SessionDescription.Type.OFFER, offer.toString());
         try {
             this.webRTCWrapper.setRemoteDescription(sdp).get();
             addIceCandidatesFromBlackLog();
-            org.webrtc.SessionDescription webRTCSessionDescription = this.webRTCWrapper.setLocalDescription().get();
+            org.webrtc.SessionDescription webRTCSessionDescription =
+                    this.webRTCWrapper.setLocalDescription().get();
             prepareSessionAccept(webRTCSessionDescription);
         } catch (final Exception e) {
             failureToAcceptSession(e);
@@ -671,18 +830,24 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
         Map.Entry<String, RtpContentMap.DescriptionTransport> foo;
         while ((foo = this.pendingIceCandidates.poll()) != null) {
             processCandidate(foo);
-            Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": added candidate from back log");
+            Log.d(
+                    Config.LOGTAG,
+                    id.account.getJid().asBareJid() + ": added candidate from back log");
         }
     }
 
-    private void prepareSessionAccept(final org.webrtc.SessionDescription webRTCSessionDescription) {
-        final SessionDescription sessionDescription = SessionDescription.parse(webRTCSessionDescription.description);
+    private void prepareSessionAccept(
+            final org.webrtc.SessionDescription webRTCSessionDescription) {
+        final SessionDescription sessionDescription =
+                SessionDescription.parse(webRTCSessionDescription.description);
         final RtpContentMap respondingRtpContentMap = RtpContentMap.of(sessionDescription);
         this.responderRtpContentMap = respondingRtpContentMap;
         storePeerDtlsSetup(respondingRtpContentMap.getDtlsSetup().flip());
         webRTCWrapper.setIsReadyToReceiveIceCandidates(true);
-        final ListenableFuture<RtpContentMap> outgoingContentMapFuture = prepareOutgoingContentMap(respondingRtpContentMap);
-        Futures.addCallback(outgoingContentMapFuture,
+        final ListenableFuture<RtpContentMap> outgoingContentMapFuture =
+                prepareOutgoingContentMap(respondingRtpContentMap);
+        Futures.addCallback(
+                outgoingContentMapFuture,
                 new FutureCallback<RtpContentMap>() {
                     @Override
                     public void onSuccess(final RtpContentMap outgoingContentMap) {
@@ -694,35 +859,56 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
                         failureToAcceptSession(throwable);
                     }
                 },
-                MoreExecutors.directExecutor()
-        );
+                MoreExecutors.directExecutor());
     }
 
     private void sendSessionAccept(final RtpContentMap rtpContentMap) {
         if (isTerminated()) {
-            Log.w(Config.LOGTAG, id.account.getJid().asBareJid() + ": preparing session accept was too slow. already terminated. nothing to do.");
+            Log.w(
+                    Config.LOGTAG,
+                    id.account.getJid().asBareJid()
+                            + ": preparing session accept was too slow. already terminated. nothing to do.");
             return;
         }
         transitionOrThrow(State.SESSION_ACCEPTED);
-        final JinglePacket sessionAccept = rtpContentMap.toJinglePacket(JinglePacket.Action.SESSION_ACCEPT, id.sessionId);
+        final JinglePacket sessionAccept =
+                rtpContentMap.toJinglePacket(JinglePacket.Action.SESSION_ACCEPT, id.sessionId);
         send(sessionAccept);
     }
 
-    private ListenableFuture<RtpContentMap> prepareOutgoingContentMap(final RtpContentMap rtpContentMap) {
+    private ListenableFuture<RtpContentMap> prepareOutgoingContentMap(
+            final RtpContentMap rtpContentMap) {
         if (this.omemoVerification.hasDeviceId()) {
-            ListenableFuture<AxolotlService.OmemoVerifiedPayload<OmemoVerifiedRtpContentMap>> verifiedPayloadFuture = id.account.getAxolotlService()
-                    .encrypt(rtpContentMap, id.with, omemoVerification.getDeviceId());
-            return Futures.transform(verifiedPayloadFuture, verifiedPayload -> {
-                omemoVerification.setOrEnsureEqual(verifiedPayload);
-                return verifiedPayload.getPayload();
-            }, MoreExecutors.directExecutor());
+            ListenableFuture<AxolotlService.OmemoVerifiedPayload<OmemoVerifiedRtpContentMap>>
+                    verifiedPayloadFuture =
+                            id.account
+                                    .getAxolotlService()
+                                    .encrypt(
+                                            rtpContentMap,
+                                            id.with,
+                                            omemoVerification.getDeviceId());
+            return Futures.transform(
+                    verifiedPayloadFuture,
+                    verifiedPayload -> {
+                        omemoVerification.setOrEnsureEqual(verifiedPayload);
+                        return verifiedPayload.getPayload();
+                    },
+                    MoreExecutors.directExecutor());
         } else {
             return Futures.immediateFuture(rtpContentMap);
         }
     }
 
-    synchronized void deliveryMessage(final Jid from, final Element message, final String serverMessageId, final long timestamp) {
-        Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": delivered message to JingleRtpConnection " + message);
+    synchronized void deliveryMessage(
+            final Jid from,
+            final Element message,
+            final String serverMessageId,
+            final long timestamp) {
+        Log.d(
+                Config.LOGTAG,
+                id.account.getJid().asBareJid()
+                        + ": delivered message to JingleRtpConnection "
+                        + message);
         switch (message.getName()) {
             case "propose":
                 receivePropose(from, Propose.upgrade(message), serverMessageId, timestamp);
@@ -745,47 +931,73 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
     }
 
     void deliverFailedProceed() {
-        Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": receive message error for proceed message");
+        Log.d(
+                Config.LOGTAG,
+                id.account.getJid().asBareJid() + ": receive message error for proceed message");
         if (transition(State.TERMINATED_CONNECTIVITY_ERROR)) {
             webRTCWrapper.close();
-            Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": transitioned into connectivity error");
+            Log.d(
+                    Config.LOGTAG,
+                    id.account.getJid().asBareJid() + ": transitioned into connectivity error");
             this.finish();
         }
     }
 
     private void receiveAccept(final Jid from, final String serverMsgId, final long timestamp) {
-        final boolean originatedFromMyself = from.asBareJid().equals(id.account.getJid().asBareJid());
+        final boolean originatedFromMyself =
+                from.asBareJid().equals(id.account.getJid().asBareJid());
         if (originatedFromMyself) {
             if (transition(State.ACCEPTED)) {
                 if (serverMsgId != null) {
                     this.message.setServerMsgId(serverMsgId);
                 }
                 this.message.setTime(timestamp);
-                this.message.setCarbon(true); //indicate that call was accepted on other device
+                this.message.setCarbon(true); // indicate that call was accepted on other device
                 this.writeLogMessageSuccess(0);
-                this.xmppConnectionService.getNotificationService().cancelIncomingCallNotification();
+                this.xmppConnectionService
+                        .getNotificationService()
+                        .cancelIncomingCallNotification();
                 this.finish();
             } else {
-                Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": unable to transition to accept because already in state=" + this.state);
+                Log.d(
+                        Config.LOGTAG,
+                        id.account.getJid().asBareJid()
+                                + ": unable to transition to accept because already in state="
+                                + this.state);
             }
         } else {
-            Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": ignoring 'accept' from " + from);
+            Log.d(
+                    Config.LOGTAG,
+                    id.account.getJid().asBareJid() + ": ignoring 'accept' from " + from);
         }
     }
 
     private void receiveReject(final Jid from, final String serverMsgId, final long timestamp) {
-        final boolean originatedFromMyself = from.asBareJid().equals(id.account.getJid().asBareJid());
-        //reject from another one of my clients
+        final boolean originatedFromMyself =
+                from.asBareJid().equals(id.account.getJid().asBareJid());
+        // reject from another one of my clients
         if (originatedFromMyself) {
             receiveRejectFromMyself(serverMsgId, timestamp);
         } else if (isInitiator()) {
             if (from.equals(id.with)) {
                 receiveRejectFromResponder();
             } else {
-                Log.d(Config.LOGTAG, id.account.getJid() + ": ignoring reject from " + from + " for session with " + id.with);
+                Log.d(
+                        Config.LOGTAG,
+                        id.account.getJid()
+                                + ": ignoring reject from "
+                                + from
+                                + " for session with "
+                                + id.with);
             }
         } else {
-            Log.d(Config.LOGTAG, id.account.getJid() + ": ignoring reject from " + from + " for session with " + id.with);
+            Log.d(
+                    Config.LOGTAG,
+                    id.account.getJid()
+                            + ": ignoring reject from "
+                            + from
+                            + " for session with "
+                            + id.with);
         }
     }