From 685e01e83fbc6e44204b470411e50aee88823134 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 21 May 2020 15:39:59 +0200 Subject: [PATCH] give TonManager control over audio mode to play dial tones on earpiece. fixes #3738 --- .../xmpp/jingle/JingleConnectionManager.java | 17 ++--- .../xmpp/jingle/ToneManager.java | 65 +++++++++++++++---- .../xmpp/jingle/WebRTCWrapper.java | 13 ++-- 3 files changed, 71 insertions(+), 24 deletions(-) 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 9ac971c2d..d8be779ef 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java @@ -38,6 +38,7 @@ import eu.siacs.conversations.services.AbstractConnectionManager; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Namespace; +import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.OnIqPacketReceived; import eu.siacs.conversations.xmpp.jingle.stanzas.Content; 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.stanzas.IqPacket; import eu.siacs.conversations.xmpp.stanzas.MessagePacket; -import eu.siacs.conversations.xmpp.Jid; public class JingleConnectionManager extends AbstractConnectionManager { static final ScheduledExecutorService SCHEDULED_EXECUTOR_SERVICE = Executors.newSingleThreadScheduledExecutor(); - final ToneManager toneManager = new ToneManager(); + final ToneManager toneManager; private final HashMap rtpSessionProposals = new HashMap<>(); private final ConcurrentHashMap connections = new ConcurrentHashMap<>(); @@ -64,6 +64,7 @@ public class JingleConnectionManager extends AbstractConnectionManager { public JingleConnectionManager(XmppConnectionService service) { super(service); + this.toneManager = new ToneManager(service); } static String nextRandomId() { @@ -333,11 +334,11 @@ public class JingleConnectionManager extends AbstractConnectionManager { } } } 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) { - if (rtpSessionProposals.remove(proposal) != null) { + if (proposal != null && rtpSessionProposals.remove(proposal) != null) { 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); } else { 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) { 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); this.rtpSessionProposals.remove(rtpSessionProposal); final MessagePacket messagePacket = mXmppConnectionService.getMessageGenerator().sessionRetract(rtpSessionProposal); @@ -527,7 +528,7 @@ public class JingleConnectionManager extends AbstractConnectionManager { final DeviceDiscoveryState preexistingState = entry.getValue(); if (preexistingState != null && preexistingState != DeviceDiscoveryState.FAILED) { final RtpEndUserState endUserState = preexistingState.toEndUserState(); - toneManager.transition(endUserState); + toneManager.transition(endUserState, media); mXmppConnectionService.notifyJingleRtpConnectionUpdate( account, with, @@ -623,7 +624,7 @@ public class JingleConnectionManager extends AbstractConnectionManager { } this.rtpSessionProposals.put(sessionProposal, target); final RtpEndUserState endUserState = target.toEndUserState(); - toneManager.transition(endUserState); + 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); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/ToneManager.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/ToneManager.java index b4e67cd36..3cdc083c0 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/ToneManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/ToneManager.java @@ -1,10 +1,10 @@ package eu.siacs.conversations.xmpp.jingle; +import android.content.Context; 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; @@ -16,27 +16,22 @@ import static java.util.Arrays.asList; class ToneManager { private final ToneGenerator toneGenerator; + private final Context context; private ToneState state = null; private ScheduledFuture currentTone; + private boolean appRtcAudioManagerHasControl = false; - ToneManager() { + ToneManager(final Context context) { ToneGenerator toneGenerator; try { - toneGenerator = new ToneGenerator(AudioManager.STREAM_MUSIC, 35); + toneGenerator = new ToneGenerator(AudioManager.STREAM_VOICE_CALL, 60); } catch (final RuntimeException e) { Log.e(Config.LOGTAG, "unable to instantiate ToneGenerator", e); toneGenerator = null; } this.toneGenerator = toneGenerator; - } - - void transition(final RtpEndUserState state) { - transition(of(true, state, Collections.emptySet())); - } - - void transition(final boolean isInitiator, final RtpEndUserState state, final Set media) { - transition(of(isInitiator, state, media)); + this.context = context; } private static ToneState of(final boolean isInitiator, final RtpEndUserState state, final Set media) { @@ -65,7 +60,15 @@ class ToneManager { return ToneState.NULL; } - private synchronized void transition(ToneState state) { + void transition(final RtpEndUserState state, final Set media) { + transition(of(true, state, media), media); + } + + void transition(final boolean isInitiator, final RtpEndUserState state, final Set media) { + transition(of(isInitiator, state, media), media); + } + + private synchronized void transition(ToneState state, final Set media) { if (this.state == state) { return; } @@ -74,6 +77,9 @@ class ToneManager { } cancelCurrentTone(); Log.d(Config.LOGTAG, getClass().getName() + ".transition(" + state + ")"); + if (state != ToneState.NULL) { + configureAudioManagerForCall(media); + } switch (state) { case RINGING: scheduleWaitingTone(); @@ -91,6 +97,10 @@ class ToneManager { this.state = state; } + void setAppRtcAudioManagerHasControl(final boolean appRtcAudioManagerHasControl) { + this.appRtcAudioManagerHasControl = appRtcAudioManagerHasControl; + } + private void scheduleConnected() { this.currentTone = JingleConnectionManager.SCHEDULED_EXECUTOR_SERVICE.schedule(() -> { startTone(ToneGenerator.TONE_PROP_PROMPT, 200); @@ -101,12 +111,14 @@ class ToneManager { this.currentTone = JingleConnectionManager.SCHEDULED_EXECUTOR_SERVICE.schedule(() -> { startTone(ToneGenerator.TONE_CDMA_CALLDROP_LITE, 375); }, 0, TimeUnit.SECONDS); + JingleConnectionManager.SCHEDULED_EXECUTOR_SERVICE.schedule(this::resetAudioManager, 375, TimeUnit.MILLISECONDS); } private void scheduleBusy() { this.currentTone = JingleConnectionManager.SCHEDULED_EXECUTOR_SERVICE.schedule(() -> { startTone(ToneGenerator.TONE_CDMA_NETWORK_BUSY, 2500); }, 0, TimeUnit.SECONDS); + JingleConnectionManager.SCHEDULED_EXECUTOR_SERVICE.schedule(this::resetAudioManager, 2500, TimeUnit.MILLISECONDS); } private void scheduleWaitingTone() { @@ -132,6 +144,35 @@ class ToneManager { } } + private void configureAudioManagerForCall(final Set 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 { NULL, RINGING, CONNECTED, BUSY, ENDING_CALL } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java index d5f009dba..6806a8142 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java @@ -53,6 +53,7 @@ import javax.annotation.Nullable; import eu.siacs.conversations.Config; import eu.siacs.conversations.services.AppRTCAudioManager; +import eu.siacs.conversations.services.XmppConnectionService; public class WebRTCWrapper { @@ -174,6 +175,7 @@ public class WebRTCWrapper { private PeerConnection peerConnection = null; private AudioTrack localAudioTrack = null; private AppRTCAudioManager appRTCAudioManager = null; + private ToneManager toneManager = null; private Context context = null; private EglBase eglBase = null; private CapturerChoice capturerChoice; @@ -206,18 +208,20 @@ public class WebRTCWrapper { 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 { PeerConnectionFactory.initialize( - PeerConnectionFactory.InitializationOptions.builder(context).createInitializationOptions() + PeerConnectionFactory.InitializationOptions.builder(service).createInitializationOptions() ); } catch (final UnsatisfiedLinkError e) { throw new InitializationException(e); } this.eglBase = EglBase.create(); - this.context = context; + this.context = service; + this.toneManager = service.getJingleConnectionManager().toneManager; mainHandler.post(() -> { - appRTCAudioManager = AppRTCAudioManager.create(context, speakerPhonePreference); + appRTCAudioManager = AppRTCAudioManager.create(service, speakerPhonePreference); + toneManager.setAppRtcAudioManagerHasControl(true); appRTCAudioManager.start(audioManagerEvents); eventCallback.onAudioDeviceChanged(appRTCAudioManager.getSelectedAudioDevice(), appRTCAudioManager.getAudioDevices()); }); @@ -288,6 +292,7 @@ public class WebRTCWrapper { this.peerConnection = null; } if (audioManager != null) { + toneManager.setAppRtcAudioManagerHasControl(false); mainHandler.post(audioManager::stop); } this.localVideoTrack = null;