give TonManager control over audio mode to play dial tones on earpiece. fixes #3738

This commit is contained in:
Daniel Gultsch 2020-05-21 15:39:59 +02:00
parent a2a7256682
commit 685e01e83f
3 changed files with 71 additions and 24 deletions

View file

@ -38,6 +38,7 @@ import eu.siacs.conversations.services.AbstractConnectionManager;
import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.OnIqPacketReceived; import eu.siacs.conversations.xmpp.OnIqPacketReceived;
import eu.siacs.conversations.xmpp.jingle.stanzas.Content; import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
import eu.siacs.conversations.xmpp.jingle.stanzas.FileTransferDescription; import eu.siacs.conversations.xmpp.jingle.stanzas.FileTransferDescription;
@ -48,11 +49,10 @@ import eu.siacs.conversations.xmpp.jingle.stanzas.Reason;
import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription; import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription;
import eu.siacs.conversations.xmpp.stanzas.IqPacket; import eu.siacs.conversations.xmpp.stanzas.IqPacket;
import eu.siacs.conversations.xmpp.stanzas.MessagePacket; import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
import eu.siacs.conversations.xmpp.Jid;
public class JingleConnectionManager extends AbstractConnectionManager { public class JingleConnectionManager extends AbstractConnectionManager {
static final ScheduledExecutorService SCHEDULED_EXECUTOR_SERVICE = Executors.newSingleThreadScheduledExecutor(); static final ScheduledExecutorService SCHEDULED_EXECUTOR_SERVICE = Executors.newSingleThreadScheduledExecutor();
final ToneManager toneManager = new ToneManager(); final ToneManager toneManager;
private final HashMap<RtpSessionProposal, DeviceDiscoveryState> rtpSessionProposals = new HashMap<>(); private final HashMap<RtpSessionProposal, DeviceDiscoveryState> rtpSessionProposals = new HashMap<>();
private final ConcurrentHashMap<AbstractJingleConnection.Id, AbstractJingleConnection> connections = new ConcurrentHashMap<>(); private final ConcurrentHashMap<AbstractJingleConnection.Id, AbstractJingleConnection> connections = new ConcurrentHashMap<>();
@ -64,6 +64,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
public JingleConnectionManager(XmppConnectionService service) { public JingleConnectionManager(XmppConnectionService service) {
super(service); super(service);
this.toneManager = new ToneManager(service);
} }
static String nextRandomId() { static String nextRandomId() {
@ -333,11 +334,11 @@ public class JingleConnectionManager extends AbstractConnectionManager {
} }
} }
} else if (addressedDirectly && "reject".equals(message.getName())) { } else if (addressedDirectly && "reject".equals(message.getName())) {
final RtpSessionProposal proposal = new RtpSessionProposal(account, from.asBareJid(), sessionId); final RtpSessionProposal proposal = getRtpSessionProposal(account, from.asBareJid(), sessionId);
synchronized (rtpSessionProposals) { synchronized (rtpSessionProposals) {
if (rtpSessionProposals.remove(proposal) != null) { 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); 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 { } 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");
@ -511,7 +512,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
private void retractSessionProposal(RtpSessionProposal rtpSessionProposal) { private void retractSessionProposal(RtpSessionProposal rtpSessionProposal) {
final Account account = rtpSessionProposal.account; final Account account = rtpSessionProposal.account;
toneManager.transition(RtpEndUserState.ENDED); 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); this.rtpSessionProposals.remove(rtpSessionProposal);
final MessagePacket messagePacket = mXmppConnectionService.getMessageGenerator().sessionRetract(rtpSessionProposal); final MessagePacket messagePacket = mXmppConnectionService.getMessageGenerator().sessionRetract(rtpSessionProposal);
@ -527,7 +528,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
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(); final RtpEndUserState endUserState = preexistingState.toEndUserState();
toneManager.transition(endUserState); toneManager.transition(endUserState, media);
mXmppConnectionService.notifyJingleRtpConnectionUpdate( mXmppConnectionService.notifyJingleRtpConnectionUpdate(
account, account,
with, with,
@ -623,7 +624,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
} }
this.rtpSessionProposals.put(sessionProposal, target); this.rtpSessionProposals.put(sessionProposal, target);
final RtpEndUserState endUserState = target.toEndUserState(); final RtpEndUserState endUserState = target.toEndUserState();
toneManager.transition(endUserState); toneManager.transition(endUserState, sessionProposal.media);
mXmppConnectionService.notifyJingleRtpConnectionUpdate(account, sessionProposal.with, sessionProposal.sessionId, 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

@ -1,10 +1,10 @@
package eu.siacs.conversations.xmpp.jingle; package eu.siacs.conversations.xmpp.jingle;
import android.content.Context;
import android.media.AudioManager; import android.media.AudioManager;
import android.media.ToneGenerator; import android.media.ToneGenerator;
import android.util.Log; import android.util.Log;
import java.util.Collections;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -16,27 +16,22 @@ import static java.util.Arrays.asList;
class ToneManager { class ToneManager {
private final ToneGenerator toneGenerator; private final ToneGenerator toneGenerator;
private final Context context;
private ToneState state = null; private ToneState state = null;
private ScheduledFuture<?> currentTone; private ScheduledFuture<?> currentTone;
private boolean appRtcAudioManagerHasControl = false;
ToneManager() { ToneManager(final Context context) {
ToneGenerator toneGenerator; ToneGenerator toneGenerator;
try { try {
toneGenerator = new ToneGenerator(AudioManager.STREAM_MUSIC, 35); toneGenerator = new ToneGenerator(AudioManager.STREAM_VOICE_CALL, 60);
} catch (final RuntimeException e) { } catch (final RuntimeException e) {
Log.e(Config.LOGTAG, "unable to instantiate ToneGenerator", e); Log.e(Config.LOGTAG, "unable to instantiate ToneGenerator", e);
toneGenerator = null; toneGenerator = null;
} }
this.toneGenerator = toneGenerator; this.toneGenerator = toneGenerator;
} this.context = context;
void transition(final RtpEndUserState state) {
transition(of(true, state, Collections.emptySet()));
}
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) { private static ToneState of(final boolean isInitiator, final RtpEndUserState state, final Set<Media> media) {
@ -65,7 +60,15 @@ class ToneManager {
return ToneState.NULL; return ToneState.NULL;
} }
private synchronized void transition(ToneState state) { void transition(final RtpEndUserState state, final Set<Media> media) {
transition(of(true, state, media), media);
}
void transition(final boolean isInitiator, final RtpEndUserState state, final Set<Media> media) {
transition(of(isInitiator, state, media), media);
}
private synchronized void transition(ToneState state, final Set<Media> media) {
if (this.state == state) { if (this.state == state) {
return; return;
} }
@ -74,6 +77,9 @@ class ToneManager {
} }
cancelCurrentTone(); cancelCurrentTone();
Log.d(Config.LOGTAG, getClass().getName() + ".transition(" + state + ")"); Log.d(Config.LOGTAG, getClass().getName() + ".transition(" + state + ")");
if (state != ToneState.NULL) {
configureAudioManagerForCall(media);
}
switch (state) { switch (state) {
case RINGING: case RINGING:
scheduleWaitingTone(); scheduleWaitingTone();
@ -91,6 +97,10 @@ class ToneManager {
this.state = state; this.state = state;
} }
void setAppRtcAudioManagerHasControl(final boolean appRtcAudioManagerHasControl) {
this.appRtcAudioManagerHasControl = appRtcAudioManagerHasControl;
}
private void scheduleConnected() { private void scheduleConnected() {
this.currentTone = JingleConnectionManager.SCHEDULED_EXECUTOR_SERVICE.schedule(() -> { this.currentTone = JingleConnectionManager.SCHEDULED_EXECUTOR_SERVICE.schedule(() -> {
startTone(ToneGenerator.TONE_PROP_PROMPT, 200); startTone(ToneGenerator.TONE_PROP_PROMPT, 200);
@ -101,12 +111,14 @@ class ToneManager {
this.currentTone = JingleConnectionManager.SCHEDULED_EXECUTOR_SERVICE.schedule(() -> { this.currentTone = JingleConnectionManager.SCHEDULED_EXECUTOR_SERVICE.schedule(() -> {
startTone(ToneGenerator.TONE_CDMA_CALLDROP_LITE, 375); startTone(ToneGenerator.TONE_CDMA_CALLDROP_LITE, 375);
}, 0, TimeUnit.SECONDS); }, 0, TimeUnit.SECONDS);
JingleConnectionManager.SCHEDULED_EXECUTOR_SERVICE.schedule(this::resetAudioManager, 375, TimeUnit.MILLISECONDS);
} }
private void scheduleBusy() { private void scheduleBusy() {
this.currentTone = JingleConnectionManager.SCHEDULED_EXECUTOR_SERVICE.schedule(() -> { this.currentTone = JingleConnectionManager.SCHEDULED_EXECUTOR_SERVICE.schedule(() -> {
startTone(ToneGenerator.TONE_CDMA_NETWORK_BUSY, 2500); startTone(ToneGenerator.TONE_CDMA_NETWORK_BUSY, 2500);
}, 0, TimeUnit.SECONDS); }, 0, TimeUnit.SECONDS);
JingleConnectionManager.SCHEDULED_EXECUTOR_SERVICE.schedule(this::resetAudioManager, 2500, TimeUnit.MILLISECONDS);
} }
private void scheduleWaitingTone() { private void scheduleWaitingTone() {
@ -132,6 +144,35 @@ class ToneManager {
} }
} }
private void configureAudioManagerForCall(final Set<Media> media) {
if (appRtcAudioManagerHasControl) {
Log.d(Config.LOGTAG, ToneManager.class.getName() + ": do not configure audio manager because RTC has control");
return;
}
final AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
if (audioManager == null) {
return;
}
final boolean isSpeakerPhone = media.contains(Media.VIDEO);
Log.d(Config.LOGTAG, ToneManager.class.getName() + ": putting AudioManager into communication mode. speaker=" + isSpeakerPhone);
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
audioManager.setSpeakerphoneOn(isSpeakerPhone);
}
private void resetAudioManager() {
if (appRtcAudioManagerHasControl) {
Log.d(Config.LOGTAG, ToneManager.class.getName() + ": do not reset audio manager because RTC has control");
return;
}
final AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
if (audioManager == null) {
return;
}
Log.d(Config.LOGTAG, ToneManager.class.getName() + ": putting AudioManager back into normal mode");
audioManager.setMode(AudioManager.MODE_NORMAL);
audioManager.setSpeakerphoneOn(false);
}
private enum ToneState { private enum ToneState {
NULL, RINGING, CONNECTED, BUSY, ENDING_CALL NULL, RINGING, CONNECTED, BUSY, ENDING_CALL
} }

View file

@ -53,6 +53,7 @@ import javax.annotation.Nullable;
import eu.siacs.conversations.Config; import eu.siacs.conversations.Config;
import eu.siacs.conversations.services.AppRTCAudioManager; import eu.siacs.conversations.services.AppRTCAudioManager;
import eu.siacs.conversations.services.XmppConnectionService;
public class WebRTCWrapper { public class WebRTCWrapper {
@ -174,6 +175,7 @@ public class WebRTCWrapper {
private PeerConnection peerConnection = null; private PeerConnection peerConnection = null;
private AudioTrack localAudioTrack = null; private AudioTrack localAudioTrack = null;
private AppRTCAudioManager appRTCAudioManager = null; private AppRTCAudioManager appRTCAudioManager = null;
private ToneManager toneManager = null;
private Context context = null; private Context context = null;
private EglBase eglBase = null; private EglBase eglBase = null;
private CapturerChoice capturerChoice; private CapturerChoice capturerChoice;
@ -206,18 +208,20 @@ public class WebRTCWrapper {
return null; return null;
} }
public void setup(final Context context, final AppRTCAudioManager.SpeakerPhonePreference speakerPhonePreference) throws InitializationException { public void setup(final XmppConnectionService service, final AppRTCAudioManager.SpeakerPhonePreference speakerPhonePreference) throws InitializationException {
try { try {
PeerConnectionFactory.initialize( PeerConnectionFactory.initialize(
PeerConnectionFactory.InitializationOptions.builder(context).createInitializationOptions() PeerConnectionFactory.InitializationOptions.builder(service).createInitializationOptions()
); );
} catch (final UnsatisfiedLinkError e) { } catch (final UnsatisfiedLinkError e) {
throw new InitializationException(e); throw new InitializationException(e);
} }
this.eglBase = EglBase.create(); this.eglBase = EglBase.create();
this.context = context; this.context = service;
this.toneManager = service.getJingleConnectionManager().toneManager;
mainHandler.post(() -> { mainHandler.post(() -> {
appRTCAudioManager = AppRTCAudioManager.create(context, speakerPhonePreference); appRTCAudioManager = AppRTCAudioManager.create(service, speakerPhonePreference);
toneManager.setAppRtcAudioManagerHasControl(true);
appRTCAudioManager.start(audioManagerEvents); appRTCAudioManager.start(audioManagerEvents);
eventCallback.onAudioDeviceChanged(appRTCAudioManager.getSelectedAudioDevice(), appRTCAudioManager.getAudioDevices()); eventCallback.onAudioDeviceChanged(appRTCAudioManager.getSelectedAudioDevice(), appRTCAudioManager.getAudioDevices());
}); });
@ -288,6 +292,7 @@ public class WebRTCWrapper {
this.peerConnection = null; this.peerConnection = null;
} }
if (audioManager != null) { if (audioManager != null) {
toneManager.setAppRtcAudioManagerHasControl(false);
mainHandler.post(audioManager::stop); mainHandler.post(audioManager::stop);
} }
this.localVideoTrack = null; this.localVideoTrack = null;