add 20s busy timeout to incoming calls

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java |  8 
src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java     | 32 
2 files changed, 38 insertions(+), 2 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java 🔗

@@ -23,6 +23,9 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 
 import eu.siacs.conversations.Config;
@@ -50,6 +53,7 @@ import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
 import rocks.xmpp.addr.Jid;
 
 public class JingleConnectionManager extends AbstractConnectionManager {
+    private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
     private final HashMap<RtpSessionProposal, DeviceDiscoveryState> rtpSessionProposals = new HashMap<>();
     private final Map<AbstractJingleConnection.Id, AbstractJingleConnection> connections = new ConcurrentHashMap<>();
 
@@ -135,6 +139,10 @@ public class JingleConnectionManager extends AbstractConnectionManager {
         return !contact.showInContactList();
     }
 
+    public ScheduledFuture<?> schedule(final Runnable runnable, final long delay, final TimeUnit timeUnit) {
+        return this.scheduledExecutorService.schedule(runnable, delay, timeUnit);
+    }
+
     public 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");

src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java 🔗

@@ -24,6 +24,8 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
 
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.entities.Account;
@@ -46,6 +48,8 @@ import rocks.xmpp.addr.Jid;
 
 public class JingleRtpConnection extends AbstractJingleConnection implements WebRTCWrapper.EventCallback {
 
+    private static final long BUSY_TIME_OUT = 20;
+
     public static final List<State> STATES_SHOWING_ONGOING_CALL = Arrays.asList(
             State.PROCEED,
             State.SESSION_INITIALIZED,
@@ -118,6 +122,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
     private RtpContentMap initiatorRtpContentMap;
     private RtpContentMap responderRtpContentMap;
     private long rtpConnectionStarted = 0; //time of 'connected'
+    private ScheduledFuture<?> ringingTimeoutFuture;
 
     JingleRtpConnection(JingleConnectionManager jingleConnectionManager, Id id, Jid initiator) {
         super(jingleConnectionManager, id, initiator);
@@ -536,9 +541,29 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
 
     private void startRinging() {
         Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": received call from " + id.with + ". start ringing");
+        ringingTimeoutFuture = jingleConnectionManager.schedule(this::ringingTimeout, BUSY_TIME_OUT, TimeUnit.SECONDS);
         xmppConnectionService.getNotificationService().showIncomingCallNotification(id, getMedia());
     }
 
+    private synchronized void ringingTimeout() {
+        Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": timeout reached for ringing");
+        switch (this.state) {
+            case PROPOSED:
+                rejectCallFromProposed();
+                break;
+            case SESSION_INITIALIZED:
+                rejectCallFromSessionInitiate();
+                break;
+        }
+    }
+
+    private void cancelRingingTimeout() {
+        final ScheduledFuture<?> future = this.ringingTimeoutFuture;
+        if (future != null && !future.isCancelled()) {
+            future.cancel(false);
+        }
+    }
+
     private void receiveProceed(final Jid from, final String serverMsgId, final long timestamp) {
         final Set<Media> media = Preconditions.checkNotNull(this.proposedMedia, "Proposed media has to be set before handling proceed");
         Preconditions.checkState(media.size() > 0, "Proposed media should not be empty");
@@ -781,17 +806,19 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
     public synchronized void acceptCall() {
         switch (this.state) {
             case PROPOSED:
+                cancelRingingTimeout();
                 acceptCallFromProposed();
                 break;
             case SESSION_INITIALIZED:
+                cancelRingingTimeout();
                 acceptCallFromSessionInitialized();
                 break;
             case ACCEPTED:
-                Log.w(Config.LOGTAG,id.account.getJid().asBareJid()+": the call has already been accepted  with another client. UI was just lagging behind");
+                Log.w(Config.LOGTAG, id.account.getJid().asBareJid() + ": the call has already been accepted  with another client. UI was just lagging behind");
                 break;
             case PROCEED:
             case SESSION_ACCEPTED:
-                Log.w(Config.LOGTAG,id.account.getJid().asBareJid()+": the call has already been accepted. user probably double tapped the UI");
+                Log.w(Config.LOGTAG, id.account.getJid().asBareJid() + ": the call has already been accepted. user probably double tapped the UI");
                 break;
             default:
                 throw new IllegalStateException("Can not accept call from " + this.state);
@@ -1069,6 +1096,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
     }
 
     private void finish() {
+        this.cancelRingingTimeout();
         this.webRTCWrapper.verifyClosed();
         this.jingleConnectionManager.finishConnection(this);
     }