give TonManager control over audio mode to play dial tones on earpiece. fixes #3738
This commit is contained in:
parent
a2a7256682
commit
685e01e83f
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue