Skip to content

Commit

Permalink
Merge pull request #38 from team-telnyx/WEBRTC-548-DTMF
Browse files Browse the repository at this point in the history
[WEBRTC-548] Added dtmf support
  • Loading branch information
Oliver-Zimmerman authored May 21, 2021
2 parents 4023ba7 + 5b9cebe commit ce077b2
Show file tree
Hide file tree
Showing 14 changed files with 131 additions and 49 deletions.
4 changes: 4 additions & 0 deletions app/src/main/java/com/telnyx/webrtc/sdk/ui/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ class MainViewModel @Inject constructor(
currentCall?.onLoudSpeakerPressed()
}

fun dtmfPressed(callId: UUID, tone: String){
currentCall?.dtmf(callId, tone)
}

fun disconnect() {
telnyxClient?.disconnect()
userManager.isUserLogin = false
Expand Down
5 changes: 5 additions & 0 deletions telnyx_rtc/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ android {

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles 'proguard-rules.txt'

buildConfigField "java.util.concurrent.atomic.AtomicBoolean", "IS_TESTING", "new java.util.concurrent.atomic.AtomicBoolean(false)"
}

buildTypes {
Expand All @@ -128,6 +130,9 @@ android {
testOptions {
unitTests.returnDefaultValues = true
}
sourceSets {
test.manifest.srcFile "src/main/AndroidManifest.xml"
}
}

dependencies {
Expand Down
27 changes: 26 additions & 1 deletion telnyx_rtc/src/main/java/com/telnyx/webrtc/sdk/Call.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ import com.telnyx.webrtc.sdk.utilities.encodeBase64
import com.telnyx.webrtc.sdk.verto.receive.*
import com.telnyx.webrtc.sdk.verto.send.*
import io.ktor.util.*
import org.webrtc.DtmfSender
import org.webrtc.IceCandidate
import org.webrtc.SessionDescription
import timber.log.Timber
import java.util.*


/**
* Class that represents a Call and handles all call related actions, including answering and ending a call.
*
Expand Down Expand Up @@ -232,7 +234,7 @@ class Call(
id = uuid,
method = SocketMethod.MODIFY.methodName,
params = ModifyParams(
sessid = sessionId,
sessionId = sessionId,
action = holdAction,
dialogParams = CallDialogParams(
callId = callId,
Expand All @@ -242,6 +244,29 @@ class Call(
socket.send(modifyMessageBody)
}

/**
* Sends Dual-Tone Multi-Frequency tones down the current peer connection.
* @param callId unique UUID of the call to send the DTMF INFO message to
* @param tone This parameter is treated as a series of characters. The characters 0
* through 9, A through D, #, and * generate the associated DTMF tones. Unrecognized characters are ignored.
*/

fun dtmf(callId: UUID, tone: String){
val uuid: String = UUID.randomUUID().toString()
val infoMessageBody = SendingMessageBody(
id = uuid,
method = SocketMethod.INFO.methodName,
params = InfoParams(
sessionId = sessionId,
dtmf = tone,
dialogParams = CallDialogParams(
callId = callId,
)
)
)
socket.send(infoMessageBody)
}

/**
* Returns call state live data
* @see [CallState]
Expand Down
3 changes: 2 additions & 1 deletion telnyx_rtc/src/main/java/com/telnyx/webrtc/sdk/Peer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ package com.telnyx.webrtc.sdk
import android.content.Context
import com.telnyx.webrtc.sdk.Config.Companion.DEFAULT_STUN
import com.telnyx.webrtc.sdk.Config.Companion.DEFAULT_TURN
import com.telnyx.webrtc.sdk.Config.Companion.USERNAME
import com.telnyx.webrtc.sdk.Config.Companion.PASSWORD
import com.telnyx.webrtc.sdk.Config.Companion.USERNAME
import com.telnyx.webrtc.sdk.socket.TxSocket
import org.webrtc.*
import timber.log.Timber
import java.util.*


/**
* Peer class that represents a peer connection which is required to initiate a call.
*
Expand Down
13 changes: 3 additions & 10 deletions telnyx_rtc/src/main/java/com/telnyx/webrtc/sdk/TelnyxClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ import org.webrtc.IceCandidate
import timber.log.Timber
import java.util.*
import com.bugsnag.android.Bugsnag
import com.bugsnag.android.Configuration
import com.bugsnag.android.ThreadSendPolicy

/**
* The TelnyxClient class that can be used to control the SDK. Create / Answer calls, change audio device, etc.
Expand Down Expand Up @@ -141,14 +139,9 @@ class TelnyxClient(
}

init {

//Initialize BugSnag
val config = Configuration.load(context)
config.projectPackages = setOf("com.telnyx.webrtc.sdk.telnyx_rtc")
config.sendThreads = ThreadSendPolicy.ALWAYS
config.enabledErrorTypes.anrs = false
config.appType = "telnyx_rtc"
Bugsnag.start(context)
if(!BuildConfig.IS_TESTING.get()) {
Bugsnag.start(context)
}

socket = TxSocket(
host_address = Config.TELNYX_HOST_ADDRESS,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package com.telnyx.webrtc.sdk.model
* @property BYE a user has ended the call
* @property MODIFY a modifier that allows the user to hold the call, etc
* @property MEDIA received media from destination, such as a dialtone
* @property MEDIA send information to the destination such as DTMF
* @property LOGIN the response to a login request.
*/
enum class SocketMethod(var methodName: String) {
Expand All @@ -24,5 +25,6 @@ enum class SocketMethod(var methodName: String) {
BYE("telnyx_rtc.bye"),
MODIFY("telnyx_rtc.modify"),
MEDIA("telnyx_rtc.media"),
INFO("telnyx_rtc.info"),
LOGIN("login")
}
34 changes: 21 additions & 13 deletions telnyx_rtc/src/main/java/com/telnyx/webrtc/sdk/socket/TxSocket.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import com.google.gson.JsonObject
import com.telnyx.webrtc.sdk.TelnyxClient
import com.telnyx.webrtc.sdk.model.SocketError.*
import com.telnyx.webrtc.sdk.model.SocketMethod.*
import com.telnyx.webrtc.sdk.telnyx_rtc.BuildConfig
import io.ktor.client.*
import io.ktor.client.engine.android.*
import io.ktor.client.features.json.*
Expand Down Expand Up @@ -163,25 +164,32 @@ class TxSocket(
}
} catch (exception: Throwable) {
Timber.d(exception)
Bugsnag.notify(exception) { event ->
// Add extra information
event.addMetadata("availableMemory", "", Runtime.getRuntime().freeMemory())
//This is not an issue, the coroutine has just been cancelled
event.severity = Severity.INFO
false
if(!BuildConfig.IS_TESTING.get()) {
Bugsnag.notify(exception) { event ->
// Add extra information
event.addMetadata(
"availableMemory",
"",
Runtime.getRuntime().freeMemory()
)
//This is not an issue, the coroutine has just been cancelled
event.severity = Severity.INFO
false
}
}
}
}
} catch (cause: Throwable) {
Timber.d(cause)
Bugsnag.notify(cause) { event ->
// Add extra information
event.addMetadata("availableMemory", "", Runtime.getRuntime().freeMemory())
//This is not an issue, the coroutine has just been cancelled
event.severity = Severity.INFO
false
if(!BuildConfig.IS_TESTING.get()) {
Bugsnag.notify(cause) { event ->
// Add extra information
event.addMetadata("availableMemory", "", Runtime.getRuntime().freeMemory())
//This is not an issue, the coroutine has just been cancelled
event.severity = Severity.INFO
false
}
}

}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import com.bugsnag.android.Bugsnag
import com.telnyx.webrtc.sdk.telnyx_rtc.BuildConfig
import timber.log.Timber

/**
Expand Down Expand Up @@ -57,7 +58,9 @@ object ConnectivityHelper {
manager.registerNetworkCallback(request, callback)
} catch (e: Exception) {
Timber.e(e,"registerNetworkStatusCallback [%s]", this@ConnectivityHelper.javaClass.simpleName)
Bugsnag.notify(e)
if(!BuildConfig.IS_TESTING.get()) {
Bugsnag.notify(e)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@ data class CallParams(val sessionId: String, val sdp: String,
val dialogParams: CallDialogParams
) : ParamRequest()

data class ByeParams(val sessid: String, val causeCode: Int,
data class ByeParams(val sessionId: String, val causeCode: Int,
val cause: String, val dialogParams: ByeDialogParams
) : ParamRequest()

data class ModifyParams(val sessid: String, val action: String,
data class ModifyParams(val sessionId: String, val action: String,
val dialogParams: CallDialogParams
) : ParamRequest()

data class InfoParams(val sessionId: String, val dtmf: String,
val dialogParams: CallDialogParams) : ParamRequest()

55 changes: 45 additions & 10 deletions telnyx_rtc/src/test/java/com/telnyx/webrtc/sdk/CallTest.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
package com.telnyx.webrtc.sdk

import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.media.AudioManager
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import com.bugsnag.android.Bugsnag
import com.bugsnag.android.Client
import com.bugsnag.android.Configuration
import com.google.gson.JsonObject
import com.telnyx.webrtc.sdk.socket.TxSocket
import com.telnyx.webrtc.sdk.telnyx_rtc.BuildConfig
import com.telnyx.webrtc.sdk.testhelpers.BaseTest
import com.telnyx.webrtc.sdk.testhelpers.extensions.CoroutinesTestExtension
import com.telnyx.webrtc.sdk.testhelpers.extensions.InstantExecutorExtension
import com.telnyx.webrtc.sdk.verto.send.SendingMessageBody
import io.ktor.client.features.websocket.*
import io.mockk.MockKAnnotations
import io.mockk.Runs
Expand All @@ -19,42 +30,51 @@ import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertDoesNotThrow
import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.ArgumentMatchers
import org.mockito.Mockito
import org.mockito.Spy
import java.util.*
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
import kotlin.test.assertEquals


@ExtendWith(InstantExecutorExtension::class, CoroutinesTestExtension::class)
class CallTest: BaseTest() {


@MockK private lateinit var mockContext: Context
@MockK
private lateinit var mockContext: Context
@Spy
lateinit var client: TelnyxClient
@MockK
@Spy
lateinit var socket: TxSocket
@Spy
lateinit var call: Call
@MockK
lateinit var audioManager: AudioManager

@BeforeEach
fun setup() {
MockKAnnotations.init(this, true, true, true)

socket = TxSocket(
host_address = "rtc.telnyx.com",
port = 14938,
)

BuildConfig.IS_TESTING.set(true);

every { mockContext.getSystemService(AppCompatActivity.AUDIO_SERVICE) } returns audioManager
every { audioManager.isMicrophoneMute = true } just Runs
every { audioManager.isSpeakerphoneOn = true } just Runs
every { audioManager.isSpeakerphoneOn} returns false
every { audioManager.isMicrophoneMute} returns false

every { audioManager.isSpeakerphoneOn} returns false
every { audioManager.isMicrophoneMute} returns false

client = TelnyxClient(mockContext)
client = Mockito.spy(
TelnyxClient(
mockContext
)
)
}

@Test
Expand Down Expand Up @@ -99,7 +119,6 @@ class CallTest: BaseTest() {
assertEquals(newCall.getIsOnLoudSpeakerStatus().getOrAwaitValue(), true)
}


@Test
fun `test new call is added to calls map - then assert that remove`() {
socket = Mockito.spy(
Expand All @@ -109,7 +128,6 @@ class CallTest: BaseTest() {
)
)

client = Mockito.spy(TelnyxClient(mockContext))
val newCall = Mockito.spy(Call(mockContext, client, socket, "123", audioManager))
newCall.callId = UUID.randomUUID()
client.addToCalls(newCall)
Expand All @@ -118,6 +136,23 @@ class CallTest: BaseTest() {
client.removeFromCalls(newCall.callId)
assert(!client.calls.containsValue(newCall))
}

@Test
fun `test dtmf pressed during call with value 2`() {
client = Mockito.spy(TelnyxClient(mockContext))
client.socket = Mockito.spy(TxSocket(
host_address = "rtc.telnyx.com",
port = 14938,
))

call = Mockito.spy(
Call(mockContext, client, client.socket, "123", audioManager)
)
call.dtmf(UUID.randomUUID(), "2")
Thread.sleep(1000)
Mockito.verify(client.socket, Mockito.times(1)).send(ArgumentMatchers.any(SendingMessageBody::class.java))
}

}

//Extension function for getOrAwaitValue for unit tests
Expand Down
15 changes: 4 additions & 11 deletions telnyx_rtc/src/test/java/com/telnyx/webrtc/sdk/PeerTest.kt
Original file line number Diff line number Diff line change
@@ -1,22 +1,13 @@
package com.telnyx.webrtc.sdk

import android.content.Context
import android.media.AudioManager
import androidx.appcompat.app.AppCompatActivity
import com.telnyx.webrtc.sdk.socket.TxSocket

import com.telnyx.webrtc.sdk.telnyx_rtc.BuildConfig
import com.telnyx.webrtc.sdk.testhelpers.BaseTest
import com.telnyx.webrtc.sdk.testhelpers.extensions.CoroutinesTestExtension
import com.telnyx.webrtc.sdk.testhelpers.extensions.InstantExecutorExtension
import io.mockk.*
import io.mockk.impl.annotations.MockK
import org.junit.Before
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertDoesNotThrow
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
import org.mockito.Spy
import org.robolectric.RuntimeEnvironment.application
import org.webrtc.PeerConnection
import org.webrtc.PeerConnectionFactory

Expand All @@ -28,6 +19,8 @@ class PeerTest : BaseTest() {

@BeforeEach
fun setup() {
BuildConfig.IS_TESTING.set(true);

MockitoAnnotations.openMocks(this)
mockkStatic(PeerConnectionFactory::class)
mockkStatic(PeerConnection::class)
Expand Down
Loading

0 comments on commit ce077b2

Please sign in to comment.