diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml
index 50c9eabadb..6b0253c5fc 100644
--- a/vector/src/main/AndroidManifest.xml
+++ b/vector/src/main/AndroidManifest.xml
@@ -14,6 +14,8 @@
+
+
diff --git a/vector/src/main/java/im/vector/riotx/features/call/CallAudioManager.kt b/vector/src/main/java/im/vector/riotx/features/call/CallAudioManager.kt
new file mode 100644
index 0000000000..a5c3069367
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/call/CallAudioManager.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.riotx.features.call
+
+import android.content.Context
+import android.content.pm.PackageManager
+import android.media.AudioManager
+import im.vector.matrix.android.api.session.call.MxCall
+import timber.log.Timber
+
+class CallAudioManager(
+ val applicationContext: Context
+) {
+
+ private val audioManager: AudioManager = applicationContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
+
+ private var savedIsSpeakerPhoneOn = false
+ private var savedIsMicrophoneMute = false
+ private var savedAudioMode = AudioManager.MODE_INVALID
+
+ private val audioFocusChangeListener = AudioManager.OnAudioFocusChangeListener { focusChange ->
+
+ // Called on the listener to notify if the audio focus for this listener has been changed.
+ // The |focusChange| value indicates whether the focus was gained, whether the focus was lost,
+ // and whether that loss is transient, or whether the new focus holder will hold it for an
+ // unknown amount of time.
+ Timber.v("## VOIP: Audio focus change $focusChange")
+ }
+
+ fun startForCall(mxCall: MxCall) {
+ Timber.v("## VOIP: AudioManager startForCall ${mxCall.callId}")
+ savedIsSpeakerPhoneOn = audioManager.isSpeakerphoneOn
+ savedIsMicrophoneMute = audioManager.isMicrophoneMute
+ savedAudioMode = audioManager.mode
+
+ // Request audio playout focus (without ducking) and install listener for changes in focus.
+
+ // Remove the deprecation forces us to use 2 different method depending on API level
+ @Suppress("DEPRECATION") val result = audioManager.requestAudioFocus(audioFocusChangeListener,
+ AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
+ if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
+ Timber.d("## VOIP Audio focus request granted for VOICE_CALL streams")
+ } else {
+ Timber.d("## VOIP Audio focus request failed")
+ }
+
+ // Start by setting MODE_IN_COMMUNICATION as default audio mode. It is
+ // required to be in this mode when playout and/or recording starts for
+ // best possible VoIP performance.
+ audioManager.mode = AudioManager.MODE_IN_COMMUNICATION
+
+ // Always disable microphone mute during a WebRTC call.
+ setMicrophoneMute(false)
+
+ // TODO check if there are headsets?
+ if (mxCall.isVideoCall) {
+ setSpeakerphoneOn(true)
+ }
+ }
+
+ fun stop() {
+ Timber.v("## VOIP: AudioManager stopCall")
+
+ // Restore previously stored audio states.
+ setSpeakerphoneOn(savedIsSpeakerPhoneOn)
+ setMicrophoneMute(savedIsMicrophoneMute)
+ audioManager.mode = savedAudioMode
+
+ @Suppress("DEPRECATION")
+ audioManager.abandonAudioFocus(audioFocusChangeListener)
+ }
+
+ /** Sets the speaker phone mode. */
+ private fun setSpeakerphoneOn(on: Boolean) {
+ Timber.v("## VOIP: AudioManager setSpeakerphoneOn $on")
+ val wasOn = audioManager.isSpeakerphoneOn
+ if (wasOn == on) {
+ return
+ }
+ audioManager.isSpeakerphoneOn = on
+ }
+
+ /** Sets the microphone mute state. */
+ private fun setMicrophoneMute(on: Boolean) {
+ Timber.v("## VOIP: AudioManager setMicrophoneMute $on")
+ val wasMuted = audioManager.isMicrophoneMute
+ if (wasMuted == on) {
+ return
+ }
+ audioManager.isMicrophoneMute = on
+
+ audioManager.isMusicActive
+ }
+
+ /** true if the device has a telephony radio with data
+ * communication support. */
+ private fun isThisPhone(): Boolean {
+ return applicationContext.packageManager.hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY)
+ }
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/call/WebRtcPeerConnectionManager.kt b/vector/src/main/java/im/vector/riotx/features/call/WebRtcPeerConnectionManager.kt
index 693b6168a6..f40d02d2f8 100644
--- a/vector/src/main/java/im/vector/riotx/features/call/WebRtcPeerConnectionManager.kt
+++ b/vector/src/main/java/im/vector/riotx/features/call/WebRtcPeerConnectionManager.kt
@@ -82,6 +82,8 @@ class WebRtcPeerConnectionManager @Inject constructor(
currentCallsListeners.remove(listener)
}
+ val audioManager = CallAudioManager(context.applicationContext)
+
data class CallContext(
val mxCall: MxCall,
@@ -457,6 +459,8 @@ class WebRtcPeerConnectionManager @Inject constructor(
Timber.v("## VOIP startOutgoingCall in room $signalingRoomId to $otherUserId isVideo $isVideoCall")
val createdCall = sessionHolder.getSafeActiveSession()?.callSignalingService()?.createOutgoingCall(signalingRoomId, otherUserId, isVideoCall) ?: return
val callContext = CallContext(createdCall)
+
+ audioManager.startForCall(createdCall)
currentCall = callContext
executor.execute {
@@ -489,11 +493,13 @@ class WebRtcPeerConnectionManager @Inject constructor(
if (currentCall != null) {
Timber.w("## VOIP TODO: Automatically reject incoming call?")
mxCall.hangUp()
+ audioManager.stop()
return
}
val callContext = CallContext(mxCall)
currentCall = callContext
+ audioManager.startForCall(mxCall)
executor.execute {
callContext.remoteCandidateSource = ReplaySubject.create()
}
@@ -538,6 +544,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
fun endCall() {
currentCall?.mxCall?.hangUp()
currentCall = null
+ audioManager.stop()
close()
}
@@ -602,7 +609,6 @@ class WebRtcPeerConnectionManager @Inject constructor(
* property until the May 13, 2016 draft of the specification.
*/
PeerConnection.PeerConnectionState.CLOSED -> {
-
}
/**
* At least one of the ICE transports for the connection is in the "disconnected" state and none of