diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index 6c9e5dd68..febe12b14 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -120,7 +120,7 @@ public final class Config { public static final boolean DISABLE_PROXY_LOOKUP = false; // disables STUN/TURN and Proxy65 look up (useful to debug IBB fallback) public static final boolean USE_DIRECT_JINGLE_CANDIDATES = true; - public static final boolean USE_JINGLE_DIRECT_INIT = true; + public static final boolean USE_JINGLE_MESSAGE_INIT = true; public static final boolean DISABLE_HTTP_UPLOAD = false; public static final boolean EXTENDED_SM_LOGGING = false; // log stanza counts public static final boolean BACKGROUND_STANZA_LOGGING = diff --git a/src/main/java/eu/siacs/conversations/services/CallIntegration.java b/src/main/java/eu/siacs/conversations/services/CallIntegration.java index b9b21e578..548a1507d 100644 --- a/src/main/java/eu/siacs/conversations/services/CallIntegration.java +++ b/src/main/java/eu/siacs/conversations/services/CallIntegration.java @@ -1,6 +1,8 @@ package eu.siacs.conversations.services; import android.content.Context; +import android.media.AudioManager; +import android.media.ToneGenerator; import android.net.Uri; import android.os.Build; import android.telecom.CallAudioState; @@ -20,11 +22,13 @@ import com.google.common.collect.Lists; import eu.siacs.conversations.Config; import eu.siacs.conversations.ui.util.MainThreadExecutor; import eu.siacs.conversations.xmpp.Jid; +import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager; import eu.siacs.conversations.xmpp.jingle.Media; import java.util.Collections; import java.util.List; import java.util.Set; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; public class CallIntegration extends Connection { @@ -32,6 +36,7 @@ public class CallIntegration extends Connection { private final AppRTCAudioManager appRTCAudioManager; private AudioDevice initialAudioDevice = null; private final AtomicBoolean initialAudioDeviceConfigured = new AtomicBoolean(false); + private final AtomicBoolean delayedDestructionInitiated = new AtomicBoolean(false); private List availableEndpoints = Collections.emptyList(); @@ -302,7 +307,9 @@ public class CallIntegration extends Connection { public void success() { Log.d(Config.LOGTAG, "CallIntegration.success()"); - this.destroyWith(new DisconnectCause(DisconnectCause.LOCAL, null)); + final var toneGenerator = new ToneGenerator(AudioManager.STREAM_MUSIC, 100); + toneGenerator.startTone(ToneGenerator.TONE_CDMA_CALLDROP_LITE, 375); + this.destroyWithDelay(new DisconnectCause(DisconnectCause.LOCAL, null), 375); } public void accepted() { @@ -316,6 +323,9 @@ public class CallIntegration extends Connection { public void error() { Log.d(Config.LOGTAG, "CallIntegration.error()"); + final var toneGenerator = new ToneGenerator(AudioManager.STREAM_MUSIC, 80); + toneGenerator.startTone(ToneGenerator.TONE_CDMA_CALLDROP_LITE, 375); + this.destroyWithDelay(new DisconnectCause(DisconnectCause.ERROR, null), 375); this.destroyWith(new DisconnectCause(DisconnectCause.ERROR, null)); } @@ -332,16 +342,33 @@ public class CallIntegration extends Connection { public void busy() { Log.d(Config.LOGTAG, "CallIntegration.busy()"); - this.destroyWith(new DisconnectCause(DisconnectCause.BUSY, null)); + final var toneGenerator = new ToneGenerator(AudioManager.STREAM_MUSIC, 80); + toneGenerator.startTone(ToneGenerator.TONE_CDMA_NETWORK_BUSY, 2500); + this.destroyWithDelay(new DisconnectCause(DisconnectCause.BUSY, null), 2500); + } + + private void destroyWithDelay(final DisconnectCause disconnectCause, final int delay) { + if (this.delayedDestructionInitiated.compareAndSet(false, true)) { + JingleConnectionManager.SCHEDULED_EXECUTOR_SERVICE.schedule( + () -> { + this.setDisconnected(disconnectCause); + this.destroy(); + }, + delay, + TimeUnit.MILLISECONDS); + } else { + Log.w(Config.LOGTAG, "CallIntegration destruction has already been scheduled!"); + } } private void destroyWith(final DisconnectCause disconnectCause) { - if (this.getState() == STATE_DISCONNECTED) { + if (this.getState() == STATE_DISCONNECTED || this.delayedDestructionInitiated.get()) { Log.d(Config.LOGTAG, "CallIntegration has already been destroyed"); return; } this.setDisconnected(disconnectCause); this.destroy(); + Log.d(Config.LOGTAG, "destroyed!"); } public static Uri address(final Jid contact) { @@ -349,7 +376,7 @@ public class CallIntegration extends Connection { } public void verifyDisconnected() { - if (this.getState() == STATE_DISCONNECTED) { + if (this.getState() == STATE_DISCONNECTED || this.delayedDestructionInitiated.get()) { return; } throw new AssertionError("CallIntegration has not been disconnected"); diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 6eea73d39..da281617e 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -1633,7 +1633,7 @@ public class ConversationFragment extends XmppFragment activity.xmppConnectionService.updateAccount(account); } final Contact contact = conversation.getContact(); - if (Config.USE_JINGLE_DIRECT_INIT && RtpCapability.jmiSupport(contact)) { + if (Config.USE_JINGLE_MESSAGE_INIT && RtpCapability.jmiSupport(contact)) { triggerRtpSession(contact.getAccount(), contact.getJid().asBareJid(), action); } else { final RtpCapability.Capability capability; 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 fd5c4c9af..e90a35a0c 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java @@ -54,9 +54,8 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; public class JingleConnectionManager extends AbstractConnectionManager { - static final ScheduledExecutorService SCHEDULED_EXECUTOR_SERVICE = + public static final ScheduledExecutorService SCHEDULED_EXECUTOR_SERVICE = Executors.newSingleThreadScheduledExecutor(); - final ToneManager toneManager; private final HashMap rtpSessionProposals = new HashMap<>(); private final ConcurrentHashMap @@ -67,7 +66,6 @@ public class JingleConnectionManager extends AbstractConnectionManager { public JingleConnectionManager(XmppConnectionService service) { super(service); - this.toneManager = new ToneManager(service); } static String nextRandomId() { @@ -490,7 +488,6 @@ public class JingleConnectionManager extends AbstractConnectionManager { proposal.callIntegration.busy(); writeLogMissedOutgoing( account, proposal.with, proposal.sessionId, serverMsgId, timestamp); - toneManager.transition(RtpEndUserState.DECLINED_OR_BUSY, proposal.media); mXmppConnectionService.notifyJingleRtpConnectionUpdate( account, proposal.with, @@ -667,7 +664,6 @@ public class JingleConnectionManager extends AbstractConnectionManager { private void retractSessionProposal(final RtpSessionProposal rtpSessionProposal) { final Account account = rtpSessionProposal.account; - toneManager.transition(RtpEndUserState.ENDED, rtpSessionProposal.media); Log.d( Config.LOGTAG, account.getJid().asBareJid() @@ -713,7 +709,6 @@ public class JingleConnectionManager extends AbstractConnectionManager { if (preexistingState != null && preexistingState != DeviceDiscoveryState.FAILED) { final RtpEndUserState endUserState = preexistingState.toEndUserState(); - toneManager.transition(endUserState, media); mXmppConnectionService.notifyJingleRtpConnectionUpdate( account, with, proposal.sessionId, endUserState); return proposal; 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 2cbd8c739..0368f1a51 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -2727,7 +2727,6 @@ public class JingleRtpConnection extends AbstractJingleConnection private void updateEndUserState() { final RtpEndUserState endUserState = getEndUserState(); - jingleConnectionManager.toneManager.transition(isInitiator(), endUserState, getMedia()); this.updateCallIntegrationState(); xmppConnectionService.notifyJingleRtpConnectionUpdate( id.account, id.with, id.sessionId, endUserState); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/ToneManager.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/ToneManager.java deleted file mode 100644 index fb82b7219..000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/ToneManager.java +++ /dev/null @@ -1,238 +0,0 @@ -package eu.siacs.conversations.xmpp.jingle; - -import android.content.Context; -import android.media.AudioManager; -import android.media.ToneGenerator; -import android.os.Build; -import android.util.Log; - -import java.util.Arrays; -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; - -import androidx.core.content.ContextCompat; - -class ToneManager { - - private ToneGenerator toneGenerator; - private final Context context; - - private ToneState state = null; - private RtpEndUserState endUserState = null; - private ScheduledFuture currentTone; - private ScheduledFuture currentResetFuture; - private boolean appRtcAudioManagerHasControl = false; - - ToneManager(final Context context) { - this.context = context; - } - - private static ToneState of(final boolean isInitiator, final RtpEndUserState state, final Set media) { - if (isInitiator) { - if (asList(RtpEndUserState.FINDING_DEVICE, 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; - } - } - if (Arrays.asList( - RtpEndUserState.CONNECTED, - RtpEndUserState.RECONNECTING, - RtpEndUserState.INCOMING_CONTENT_ADD) - .contains(state)) { - if (media.contains(Media.VIDEO)) { - return ToneState.NULL; - } else { - return ToneState.CONNECTED; - } - } - return ToneState.NULL; - } - - void transition(final RtpEndUserState state, final Set media) { - transition(state, of(true, state, media), media); - } - - void transition(final boolean isInitiator, final RtpEndUserState state, final Set media) { - transition(state, of(isInitiator, state, media), media); - } - - private synchronized void transition(final RtpEndUserState endUserState, final ToneState state, final Set media) { - final RtpEndUserState normalizeEndUserState = normalize(endUserState); - if (this.endUserState == normalizeEndUserState) { - return; - } - this.endUserState = normalizeEndUserState; - if (this.state == state) { - return; - } - if (state == ToneState.NULL && this.state == ToneState.ENDING_CALL) { - return; - } - cancelCurrentTone(); - Log.d(Config.LOGTAG, getClass().getName() + ".transition(" + state + ")"); - if (state != ToneState.NULL) { - configureAudioManagerForCall(media); - } - switch (state) { - case RINGING: - // ringing can be removed as this is now handled by 'CallIntegration' - //scheduleWaitingTone(); - break; - case CONNECTED: - scheduleConnected(); - break; - case BUSY: - scheduleBusy(); - break; - case ENDING_CALL: - scheduleEnding(); - break; - case NULL: - if (noResetScheduled()) { - resetAudioManager(); - } - break; - default: - throw new IllegalStateException("Unable to handle transition to "+state); - } - this.state = state; - } - - private static RtpEndUserState normalize(final RtpEndUserState endUserState) { - if (Arrays.asList( - RtpEndUserState.CONNECTED, - RtpEndUserState.RECONNECTING, - RtpEndUserState.INCOMING_CONTENT_ADD) - .contains(endUserState)) { - return RtpEndUserState.CONNECTED; - } else { - return endUserState; - } - } - - void setAppRtcAudioManagerHasControl(final boolean appRtcAudioManagerHasControl) { - this.appRtcAudioManagerHasControl = appRtcAudioManagerHasControl; - } - - private void scheduleConnected() { - this.currentTone = JingleConnectionManager.SCHEDULED_EXECUTOR_SERVICE.schedule(() -> { - startTone(ToneGenerator.TONE_PROP_PROMPT, 200); - }, 0, TimeUnit.SECONDS); - } - - private void scheduleEnding() { - this.currentTone = JingleConnectionManager.SCHEDULED_EXECUTOR_SERVICE.schedule(() -> { - startTone(ToneGenerator.TONE_CDMA_CALLDROP_LITE, 375); - }, 0, TimeUnit.SECONDS); - this.currentResetFuture = 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); - this.currentResetFuture = JingleConnectionManager.SCHEDULED_EXECUTOR_SERVICE.schedule(this::resetAudioManager, 2500, TimeUnit.MILLISECONDS); - } - - private void scheduleWaitingTone() { - this.currentTone = JingleConnectionManager.SCHEDULED_EXECUTOR_SERVICE.scheduleAtFixedRate(() -> { - startTone(ToneGenerator.TONE_CDMA_DIAL_TONE_LITE, 750); - }, 0, 3, TimeUnit.SECONDS); - } - - private boolean noResetScheduled() { - return this.currentResetFuture == null || this.currentResetFuture.isDone(); - } - - private void cancelCurrentTone() { - if (currentTone != null) { - currentTone.cancel(true); - } - stopTone(toneGenerator); - } - - private static void stopTone(final ToneGenerator toneGenerator) { - if (toneGenerator == null) { - return; - } - try { - toneGenerator.stopTone(); - } catch (final RuntimeException e) { - Log.w(Config.LOGTAG,"tone has already stopped"); - } - } - - private void startTone(final int toneType, final int durationMs) { - if (this.toneGenerator != null) { - this.toneGenerator.release();; - - } - final AudioManager audioManager = ContextCompat.getSystemService(context, AudioManager.class); - final boolean ringerModeNormal = audioManager == null || audioManager.getRingerMode() == AudioManager.RINGER_MODE_NORMAL; - this.toneGenerator = getToneGenerator(ringerModeNormal); - if (toneGenerator != null) { - this.toneGenerator.startTone(toneType, durationMs); - } - } - - private static ToneGenerator getToneGenerator(final boolean ringerModeNormal) { - try { - // when silent and on Android 12+ use STREAM_MUSIC - if (ringerModeNormal || Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { - return new ToneGenerator(AudioManager.STREAM_VOICE_CALL,60); - } else { - return new ToneGenerator(AudioManager.STREAM_MUSIC,100); - } - } catch (final Exception e) { - Log.d(Config.LOGTAG,"could not create tone generator",e); - return null; - } - } - - 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 128a35bf0..8bc7c6f6f 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java @@ -2,8 +2,6 @@ package eu.siacs.conversations.xmpp.jingle; import android.content.Context; import android.os.Build; -import android.os.Handler; -import android.os.Looper; import android.util.Log; import com.google.common.base.Optional; @@ -15,8 +13,6 @@ import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.SettableFuture; import eu.siacs.conversations.Config; -import eu.siacs.conversations.services.AppRTCAudioManager; -import eu.siacs.conversations.services.CallIntegration; import eu.siacs.conversations.services.XmppConnectionService; import org.webrtc.AudioSource; @@ -52,7 +48,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import javax.annotation.Nonnull; import javax.annotation.Nullable; -@SuppressWarnings("UnstableApiUsage") public class WebRTCWrapper { private static final String EXTENDED_LOGGING_TAG = WebRTCWrapper.class.getSimpleName(); @@ -205,7 +200,6 @@ public class WebRTCWrapper { }; @Nullable private PeerConnectionFactory peerConnectionFactory = null; @Nullable private PeerConnection peerConnection = null; - private ToneManager toneManager = null; private Context context = null; private EglBase eglBase = null; private VideoSourceWrapper videoSourceWrapper; @@ -222,8 +216,7 @@ public class WebRTCWrapper { } } - public void setup(final XmppConnectionService service) - throws InitializationException { + public void setup(final XmppConnectionService service) throws InitializationException { try { PeerConnectionFactory.initialize( PeerConnectionFactory.InitializationOptions.builder(service) @@ -238,7 +231,6 @@ public class WebRTCWrapper { throw new InitializationException("Unable to create EGL base", e); } this.context = service; - this.toneManager = service.getJingleConnectionManager().toneManager; } synchronized void initializePeerConnection(