diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java index 07d6a25cb..df05e631b 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java +++ b/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 rtpSessionProposals = new HashMap<>(); private final Map 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"); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index 476c463d7..adc4c43e4 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/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 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 = 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); }