play busy and dial tones

This commit is contained in:
Daniel Gultsch 2020-04-27 17:51:38 +02:00
parent 07911b2094
commit fc4397e5b9
3 changed files with 123 additions and 7 deletions

View file

@ -50,9 +50,10 @@ import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
import rocks.xmpp.addr.Jid; import rocks.xmpp.addr.Jid;
public class JingleConnectionManager extends AbstractConnectionManager { public class JingleConnectionManager extends AbstractConnectionManager {
private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); public static final ScheduledExecutorService SCHEDULED_EXECUTOR_SERVICE = Executors.newSingleThreadScheduledExecutor();
public final ToneManager toneManager = new ToneManager();
private final HashMap<RtpSessionProposal, DeviceDiscoveryState> rtpSessionProposals = new HashMap<>(); private final HashMap<RtpSessionProposal, DeviceDiscoveryState> rtpSessionProposals = new HashMap<>();
private final Map<AbstractJingleConnection.Id, AbstractJingleConnection> connections = new ConcurrentHashMap<>(); private final ConcurrentHashMap<AbstractJingleConnection.Id, AbstractJingleConnection> connections = new ConcurrentHashMap<>();
private final Cache<PersistableSessionId, JingleRtpConnection.State> endedSessions = CacheBuilder.newBuilder() private final Cache<PersistableSessionId, JingleRtpConnection.State> endedSessions = CacheBuilder.newBuilder()
.expireAfterWrite(30, TimeUnit.MINUTES) .expireAfterWrite(30, TimeUnit.MINUTES)
@ -141,7 +142,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
} }
ScheduledFuture<?> schedule(final Runnable runnable, final long delay, final TimeUnit timeUnit) { ScheduledFuture<?> schedule(final Runnable runnable, final long delay, final TimeUnit timeUnit) {
return this.scheduledExecutorService.schedule(runnable, delay, timeUnit); return this.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) {
@ -268,6 +269,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
synchronized (rtpSessionProposals) { synchronized (rtpSessionProposals) {
if (rtpSessionProposals.remove(proposal) != null) { if (rtpSessionProposals.remove(proposal) != null) {
writeLogMissedOutgoing(account, proposal.with, proposal.sessionId, serverMsgId, timestamp); writeLogMissedOutgoing(account, proposal.with, proposal.sessionId, serverMsgId, timestamp);
toneManager.transition(true, RtpEndUserState.DECLINED_OR_BUSY);
mXmppConnectionService.notifyJingleRtpConnectionUpdate(account, proposal.with, proposal.sessionId, RtpEndUserState.DECLINED_OR_BUSY); mXmppConnectionService.notifyJingleRtpConnectionUpdate(account, proposal.with, proposal.sessionId, RtpEndUserState.DECLINED_OR_BUSY);
} else { } 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");
@ -352,7 +354,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
} }
public Optional<AbstractJingleConnection.Id> getOngoingRtpConnection(final Contact contact) { public Optional<AbstractJingleConnection.Id> 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) { if (entry.getValue() instanceof JingleRtpConnection) {
final AbstractJingleConnection.Id id = entry.getKey(); 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())) {
@ -423,6 +425,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
} }
} }
if (matchingProposal != null) { if (matchingProposal != null) {
toneManager.transition(true, RtpEndUserState.ENDED);
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": retracting rtp session proposal with " + with); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": retracting rtp session proposal with " + with);
this.rtpSessionProposals.remove(matchingProposal); this.rtpSessionProposals.remove(matchingProposal);
final MessagePacket messagePacket = mXmppConnectionService.getMessageGenerator().sessionRetract(matchingProposal); final MessagePacket messagePacket = mXmppConnectionService.getMessageGenerator().sessionRetract(matchingProposal);
@ -439,11 +442,13 @@ public class JingleConnectionManager extends AbstractConnectionManager {
if (proposal.account == account && with.asBareJid().equals(proposal.with)) { if (proposal.account == account && with.asBareJid().equals(proposal.with)) {
final DeviceDiscoveryState preexistingState = entry.getValue(); final DeviceDiscoveryState preexistingState = entry.getValue();
if (preexistingState != null && preexistingState != DeviceDiscoveryState.FAILED) { if (preexistingState != null && preexistingState != DeviceDiscoveryState.FAILED) {
final RtpEndUserState endUserState = preexistingState.toEndUserState();
toneManager.transition(true, endUserState);
mXmppConnectionService.notifyJingleRtpConnectionUpdate( mXmppConnectionService.notifyJingleRtpConnectionUpdate(
account, account,
with, with,
proposal.sessionId, proposal.sessionId,
preexistingState.toEndUserState() endUserState
); );
return; return;
} }
@ -529,7 +534,9 @@ public class JingleConnectionManager extends AbstractConnectionManager {
return; return;
} }
this.rtpSessionProposals.put(sessionProposal, target); this.rtpSessionProposals.put(sessionProposal, target);
mXmppConnectionService.notifyJingleRtpConnectionUpdate(account, sessionProposal.with, sessionProposal.sessionId, target.toEndUserState()); final RtpEndUserState endUserState = target.toEndUserState();
toneManager.transition(true, endUserState);
mXmppConnectionService.notifyJingleRtpConnectionUpdate(account, sessionProposal.with, sessionProposal.sessionId, endUserState);
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": flagging session " + sessionId + " as " + target); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": flagging session " + sessionId + " as " + target);
} }
} }

View file

@ -1027,7 +1027,11 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
} }
private void updateEndUserState() { private void updateEndUserState() {
xmppConnectionService.notifyJingleRtpConnectionUpdate(id.account, id.with, id.sessionId, getEndUserState()); final RtpEndUserState endUserState = getEndUserState();
final RtpContentMap contentMap = initiatorRtpContentMap;
final Set<Media> media = contentMap == null ? Collections.emptySet() : contentMap.getMedia();
jingleConnectionManager.toneManager.transition(isInitiator(), endUserState, media);
xmppConnectionService.notifyJingleRtpConnectionUpdate(id.account, id.with, id.sessionId, endUserState);
} }
private void updateOngoingCallNotification() { private void updateOngoingCallNotification() {

View file

@ -0,0 +1,105 @@
package eu.siacs.conversations.xmpp.jingle;
import android.media.AudioManager;
import android.media.ToneGenerator;
import android.util.Log;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import eu.siacs.conversations.Config;
import static java.util.Arrays.asList;
public class ToneManager {
private final ToneGenerator toneGenerator;
private ToneState state = null;
private ScheduledFuture<?> currentTone;
public ToneManager() {
this.toneGenerator = new ToneGenerator(AudioManager.STREAM_VOICE_CALL, 35);
}
public void transition(final boolean isInitiator, final RtpEndUserState state) {
transition(of(isInitiator, state, Collections.emptySet()));
}
public void transition(final boolean isInitiator, final RtpEndUserState state, final Set<Media> media) {
transition(of(isInitiator, state, media));
}
private static ToneState of(final boolean isInitiator, final RtpEndUserState state, final Set<Media> media) {
if (isInitiator) {
if (asList(RtpEndUserState.RINGING, RtpEndUserState.CONNECTING).contains(state)) {
return ToneState.RINGING;
}
if (state == RtpEndUserState.DECLINED_OR_BUSY) {
return ToneState.BUSY;
}
}
if (state == RtpEndUserState.ENDING_CALL) {
if (media.contains(Media.VIDEO)) {
return ToneState.NULL;
} else {
return ToneState.ENDING_CALL;
}
}
return ToneState.NULL;
}
private synchronized void transition(ToneState state) {
if (this.state == state) {
return;
}
if (state == ToneState.NULL && this.state == ToneState.ENDING_CALL) {
return;
}
cancelCurrentTone();
Log.d(Config.LOGTAG, getClass().getName() + ".transition(" + state + ")");
switch (state) {
case RINGING:
scheduleWaitingTone();
break;
case BUSY:
scheduleBusy();
break;
case ENDING_CALL:
scheduleEnding();
break;
}
this.state = state;
}
private void scheduleEnding() {
this.currentTone = JingleConnectionManager.SCHEDULED_EXECUTOR_SERVICE.schedule(() -> {
this.toneGenerator.startTone(ToneGenerator.TONE_CDMA_CONFIRM, 600);
}, 0, TimeUnit.SECONDS);
}
private void scheduleBusy() {
this.currentTone = JingleConnectionManager.SCHEDULED_EXECUTOR_SERVICE.schedule(() -> {
this.toneGenerator.startTone(ToneGenerator.TONE_CDMA_NETWORK_BUSY, 2500);
}, 0, TimeUnit.SECONDS);
}
private void scheduleWaitingTone() {
this.currentTone = JingleConnectionManager.SCHEDULED_EXECUTOR_SERVICE.scheduleAtFixedRate(() -> {
this.toneGenerator.startTone(ToneGenerator.TONE_CDMA_DIAL_TONE_LITE, 750);
}, 0, 3, TimeUnit.SECONDS);
}
private void cancelCurrentTone() {
if (currentTone != null) {
currentTone.cancel(true);
}
toneGenerator.stopTone();
}
private enum ToneState {
NULL, RINGING, BUSY, ENDING_CALL
}
}