From 594ad7b9bd8356153b275801a460c4f5fe730bcd Mon Sep 17 00:00:00 2001 From: AriZavala2 <77034370+AriZavala2@users.noreply.github.com> Date: Tue, 7 Dec 2021 12:56:17 -0800 Subject: [PATCH 01/30] Adding Quickstart for Network Traversal Java SDK (#22) * Adding Quickstart for Network Traversal Java SDK * Fix identation * Fix identation pt. 2 * Update PR * Update PR --- get-relay-config-quickstart/README.md | 46 ++++++++ get-relay-config-quickstart/pom.xml | 101 ++++++++++++++++++ .../com/communication/quickstart/App.java | 101 ++++++++++++++++++ 3 files changed, 248 insertions(+) create mode 100644 get-relay-config-quickstart/README.md create mode 100644 get-relay-config-quickstart/pom.xml create mode 100644 get-relay-config-quickstart/src/main/java/com/communication/quickstart/App.java diff --git a/get-relay-config-quickstart/README.md b/get-relay-config-quickstart/README.md new file mode 100644 index 00000000..03a631ac --- /dev/null +++ b/get-relay-config-quickstart/README.md @@ -0,0 +1,46 @@ +--- +page_type: sample +languages: +- Java +products: +- azure +- azure-communication-networktraversal +- azure-communication-common +--- + + +# Get a relay Configuration + +## Prerequisites + +- An Azure account with an active subscription. [Create an account for free](https://azure.microsoft.com/free/?WT.mc_id=A261C142F). +- [Java Development Kit (JDK)](https://docs.microsoft.com/azure/developer/java/fundamentals/java-jdk-install) version 8 or above +- [Apache Maven](https://maven.apache.org/download.cgi) +- An deployed Communication Services resource and connection string. For details, see [Create a Communication Services resource](https://docs.microsoft.com/azure/communication-services/quickstarts/create-communication-resource). + +## Code Structure + +- **./get-relay-config-quickstart/src/main/java/com/communication/quickstart/App.java:** contains code for getting a relay configuration. +- **pom.xml:** Project's Project Object Model, or [POM](https://maven.apache.org/guides/introduction/introduction-to-the-pom.html). + +## Before running sample code + +1. Open an instance of PowerShell, Windows Terminal, Command Prompt or equivalent and navigate to the directory that you'd like to clone the sample to. +2. `git clone https://github.com/Azure-Samples/communication-services-java-quickstarts.git` +3. With the Communication Services procured in pre-requisites, add connection string in the code at line no 14 + ```String connectionString = "https://.communication.azure.com/;accesskey=";```. + +## Run the code + +1. Navigate to the directory containing the pom.xml file and compile the project by using command `mvn compile`. +2. Then, build the package using command `mvn package`. +3. Run the command to execute the app `mvn exec:java -Dexec.mainClass="com.communication.quickstart.App" -Dexec.cleanupDaemonThreads=false`. If you are on Windows, run the following command: `mvn exec:java -D"exec.mainClass"="com.communication.quickstart.App" -D"exec.cleanupDaemonThreads"="false"` + +## Next Steps + +Take a look at our [API Documentation][apiref] for more information about the APIs that are available in the clients: + +[getrelayconfiguration]: https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/communication/azure-communication-networktraversal/src/samples/java/com/azure/communication/networktraversal +[freesub]: https://azure.microsoft.com/free/ +[createinstance_azurecommunicationservicesaccount]: https://docs.microsoft.com/azure/communication-services/quickstarts/create-communication-resource +[package]: https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/communication/azure-communication-networktraversal/README.md diff --git a/get-relay-config-quickstart/pom.xml b/get-relay-config-quickstart/pom.xml new file mode 100644 index 00000000..a87092d0 --- /dev/null +++ b/get-relay-config-quickstart/pom.xml @@ -0,0 +1,101 @@ + + + + 4.0.0 + + com.communication.quickstart + communication-quickstart + 1.0-SNAPSHOT + + communication-quickstart + + http://www.example.com + + + UTF-8 + 1.7 + 1.7 + + + + + junit + junit + 4.11 + test + + + com.azure + azure-communication-networktraversal + 1.0.0-beta.2 + + + com.azure + azure-communication-common + 1.0.3 + + + com.azure + azure-communication-identity + 1.1.3 + + + io.netty + netty-all + 4.1.68.Final + + + org.kurento + kurento-client + 6.15.0 + + + + + + + + + + maven-clean-plugin + 3.1.0 + + + + maven-resources-plugin + 3.0.2 + + + maven-compiler-plugin + 3.8.0 + + + maven-surefire-plugin + 2.22.1 + + + maven-jar-plugin + 3.0.2 + + + maven-install-plugin + 2.5.2 + + + maven-deploy-plugin + 2.8.2 + + + + maven-site-plugin + 3.7.1 + + + maven-project-info-reports-plugin + 3.0.0 + + + + + diff --git a/get-relay-config-quickstart/src/main/java/com/communication/quickstart/App.java b/get-relay-config-quickstart/src/main/java/com/communication/quickstart/App.java new file mode 100644 index 00000000..ca759110 --- /dev/null +++ b/get-relay-config-quickstart/src/main/java/com/communication/quickstart/App.java @@ -0,0 +1,101 @@ +package com.communication.quickstart; + +import com.azure.communication.common.CommunicationUserIdentifier; +import com.azure.communication.identity.CommunicationIdentityClient; +import com.azure.communication.identity.CommunicationIdentityClientBuilder; +import com.azure.communication.networktraversal.*; +import com.azure.communication.networktraversal.models.*; +import org.kurento.client.WebRtcEndpoint; +import org.kurento.client.MediaPipeline; +import org.kurento.client.KurentoClient; +import java.util.List; +import java.lang.reflect.*; + +public class App +{ + private static KurentoClient kurento; + + // You can find your connection string from your resource in the Azure portal + private static String connectionString = "https://.communication.azure.com/;accesskey="; + private static CommunicationIdentityClient communicationIdentityClient = new CommunicationIdentityClientBuilder() + .connectionString(connectionString) + .buildClient(); + + private static CommunicationRelayClient communicationRelayClient = new CommunicationRelayClientBuilder() + .connectionString(connectionString) + .buildClient(); + + public static void main(String[] args) + { + System.out.println("Azure Communication Services - NetworkTraversal Quickstart"); + + System.out.println("Getting a relay configuration"); + getRelayConfiguration(); + + System.out.println("Getting a relay configuration using Identity"); + getRelayConfigurationUsingIdentity(); + + System.out.println("Getting a relay configuration passing a Route Type"); + getRelayConfigurationUsingRouteType(); + } + + public static void getRelayConfiguration() + { + CommunicationRelayConfiguration config = communicationRelayClient.getRelayConfiguration(); + + System.out.println("Expires on:" + config.getExpiresOn()); + List iceServers = config.getIceServers(); + + for (CommunicationIceServer iceS : iceServers) { + System.out.println("URLS: " + iceS.getUrls()); + System.out.println("Username: " + iceS.getUsername()); + System.out.println("credential: " + iceS.getCredential()); + System.out.println("RouteType: " + iceS.getRouteType()); + } + + // Now you can configure your WebRtcEndpoint to use TURN credentials + + // MediaPipeline pipeline = kurento.createMediaPipeline(); + // WebRtcEndpoint webRtcEndpoint = new WebRtcEndpoint.Builder(pipeline).build(); + + // CommunicationIceServer iceServerToUse = iceServers.get(0); + // String urlToUse = iceServerToUse.getUrls().get(0); + + // Format for URL must be user:password@ipaddress:port + // String ipAndPort = urlToUse.substring(5, urlToUse.length()); + // webRtcEndpoint.setTurnUrl(iceServerToUse.getUsername()+ ":" + iceServerToUse.getCredential() + "@" + ipAndPort); + } + + public static void getRelayConfigurationUsingIdentity() + { + CommunicationUserIdentifier user = communicationIdentityClient.createUser(); + System.out.println("User id: " + user.getId()); + + CommunicationRelayConfiguration config = communicationRelayClient.getRelayConfiguration(user); + + System.out.println("Expires on:" + config.getExpiresOn()); + List iceServers = config.getIceServers(); + + for (CommunicationIceServer iceS : iceServers) { + System.out.println("URLS: " + iceS.getUrls()); + System.out.println("Username: " + iceS.getUsername()); + System.out.println("credential: " + iceS.getCredential()); + System.out.println("RouteType: " + iceS.getRouteType()); + } + } + + public static void getRelayConfigurationUsingRouteType() + { + CommunicationRelayConfiguration config = communicationRelayClient.getRelayConfiguration(RouteType.NEAREST); + + System.out.println("Expires on:" + config.getExpiresOn()); + List iceServers = config.getIceServers(); + + for (CommunicationIceServer iceS : iceServers) { + System.out.println("URLS: " + iceS.getUrls()); + System.out.println("Username: " + iceS.getUsername()); + System.out.println("credential: " + iceS.getCredential()); + System.out.println("RouteType: " + iceS.getRouteType()); + } + } +} From 6a814a3cc98eea20b54f979f9d10fe3ee0242b31 Mon Sep 17 00:00:00 2001 From: Fang Chen <16232002+fangchen0601@users.noreply.github.com> Date: Wed, 8 Dec 2021 12:14:54 -0800 Subject: [PATCH 02/30] init project --- IncomingCallQuickstart/pom.xml | 75 +++++++++++++++++++ .../incomingcallquickstart/App.java | 13 ++++ 2 files changed, 88 insertions(+) create mode 100644 IncomingCallQuickstart/pom.xml create mode 100644 IncomingCallQuickstart/src/main/java/com/communication/incomingcallquickstart/App.java diff --git a/IncomingCallQuickstart/pom.xml b/IncomingCallQuickstart/pom.xml new file mode 100644 index 00000000..1a9de5e3 --- /dev/null +++ b/IncomingCallQuickstart/pom.xml @@ -0,0 +1,75 @@ + + + + 4.0.0 + + com.communication.incomingcallquickstart + communication-incomingcallquickstart + 1.0-SNAPSHOT + + communication-incomingcallquickstart + + http://www.example.com + + + UTF-8 + 1.7 + 1.7 + + + + + junit + junit + 4.11 + test + + + + + + + + + maven-clean-plugin + 3.1.0 + + + + maven-resources-plugin + 3.0.2 + + + maven-compiler-plugin + 3.8.0 + + + maven-surefire-plugin + 2.22.1 + + + maven-jar-plugin + 3.0.2 + + + maven-install-plugin + 2.5.2 + + + maven-deploy-plugin + 2.8.2 + + + + maven-site-plugin + 3.7.1 + + + maven-project-info-reports-plugin + 3.0.0 + + + + + diff --git a/IncomingCallQuickstart/src/main/java/com/communication/incomingcallquickstart/App.java b/IncomingCallQuickstart/src/main/java/com/communication/incomingcallquickstart/App.java new file mode 100644 index 00000000..60569b18 --- /dev/null +++ b/IncomingCallQuickstart/src/main/java/com/communication/incomingcallquickstart/App.java @@ -0,0 +1,13 @@ +package com.communication.incomingcallquickstart; + +/** + * Hello world! + * + */ +public class App +{ + public static void main( String[] args ) + { + System.out.println( "Hello World!" ); + } +} From 3ce60bf02a0f04347628626af80019ed6bfd39be Mon Sep 17 00:00:00 2001 From: Fang Chen <16232002+fangchen0601@users.noreply.github.com> Date: Mon, 20 Dec 2021 11:15:34 -0800 Subject: [PATCH 03/30] update incomingcallquickstart --- IncomingCallQuickstart/pom.xml | 98 +++++++++++++++++-- .../incomingcallquickstart/App.java | 21 +++- .../ConfigurationManager.java | 43 ++++++++ .../Controllers/InComingCallController.java | 26 +++++ .../incomingcallquickstart/config.properties | 30 ++++++ 5 files changed, 205 insertions(+), 13 deletions(-) create mode 100644 IncomingCallQuickstart/src/main/java/com/communication/incomingcallquickstart/ConfigurationManager.java create mode 100644 IncomingCallQuickstart/src/main/java/com/communication/incomingcallquickstart/Controllers/InComingCallController.java create mode 100644 IncomingCallQuickstart/src/main/java/com/communication/incomingcallquickstart/config.properties diff --git a/IncomingCallQuickstart/pom.xml b/IncomingCallQuickstart/pom.xml index 1a9de5e3..7d5fac97 100644 --- a/IncomingCallQuickstart/pom.xml +++ b/IncomingCallQuickstart/pom.xml @@ -4,6 +4,13 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.4 + + + com.communication.incomingcallquickstart communication-incomingcallquickstart 1.0-SNAPSHOT @@ -25,17 +32,82 @@ 4.11 test + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + test + + + org.apache.httpcomponents + httpclient + 4.5.13 + + + com.azure + azure-core + 1.19.0 + + + com.azure + azure-identity + 1.3.5 + + + com.azure + azure-communication-identity + 1.1.4 + + + com.azure + azure-communication-common + + + + + com.azure + azure-communication-callingserver + 1.0.0-beta.4 + + + com.microsoft.cognitiveservices.speech + client-sdk + 1.12.1 + + + com.azure + azure-messaging-eventgrid + 4.6.0 + + + com.azure + azure-cosmos + 4.18.0 + + + + maven-cognitiveservices-speech + Microsoft Cognitive Services Speech Maven Repository + https://csspeechstorage.blob.core.windows.net/maven/ + + + - + - + + org.springframework.boot + spring-boot-maven-plugin + maven-clean-plugin 3.1.0 - maven-resources-plugin 3.0.2 @@ -52,15 +124,10 @@ maven-jar-plugin 3.0.2 - - maven-install-plugin - 2.5.2 - maven-deploy-plugin 2.8.2 - maven-site-plugin 3.7.1 @@ -69,6 +136,21 @@ maven-project-info-reports-plugin 3.0.0 + + org.codehaus.mojo + exec-maven-plugin + 3.0.0 + + + + java + + + + + com.communication.incomingcallquickstart.App + + diff --git a/IncomingCallQuickstart/src/main/java/com/communication/incomingcallquickstart/App.java b/IncomingCallQuickstart/src/main/java/com/communication/incomingcallquickstart/App.java index 60569b18..422a0ea1 100644 --- a/IncomingCallQuickstart/src/main/java/com/communication/incomingcallquickstart/App.java +++ b/IncomingCallQuickstart/src/main/java/com/communication/incomingcallquickstart/App.java @@ -1,13 +1,24 @@ package com.communication.incomingcallquickstart; -/** - * Hello world! - * - */ +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import java.util.*; + +@SpringBootApplication public class App { + final static String url = "http://localhost:9007"; + final static String serverPort = "9007"; public static void main( String[] args ) { - System.out.println( "Hello World!" ); + SpringApplication application = new SpringApplication(App.class); + application.setDefaultProperties(Collections.singletonMap("server.port", serverPort)); + application.run(args); + + // Logger.logMessage(Logger.MessageType.INFORMATION, "Starting ACS InComing Call Sample App "); + + // Get configuration properties + ConfigurationManager configurationManager = ConfigurationManager.getInstance(); + configurationManager.loadAppSettings(); } } diff --git a/IncomingCallQuickstart/src/main/java/com/communication/incomingcallquickstart/ConfigurationManager.java b/IncomingCallQuickstart/src/main/java/com/communication/incomingcallquickstart/ConfigurationManager.java new file mode 100644 index 00000000..87841b16 --- /dev/null +++ b/IncomingCallQuickstart/src/main/java/com/communication/incomingcallquickstart/ConfigurationManager.java @@ -0,0 +1,43 @@ +package com.communication.incomingcallquickstart; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.Properties; + +public class ConfigurationManager { + private static ConfigurationManager configurationManager = null; + private final Properties appSettings = new Properties(); + + private ConfigurationManager() { + } + + // static method to create instance of ConfigurationManager class + public static ConfigurationManager getInstance() { + if (configurationManager == null) { + configurationManager = new ConfigurationManager(); + } + return configurationManager; + } + + public void loadAppSettings() { + try { + File configFile = new File("src/main/java/com/communication/incomingcallquickstart/config.properties"); + FileReader reader = new FileReader(configFile); + appSettings.load(reader); + reader.close(); + } catch (FileNotFoundException ex) { + // Logger.logMessage(Logger.MessageType.INFORMATION,"Loading app settings failed with error -- > " + ex.getMessage()); + } catch (IOException ex) { + // Logger.logMessage(Logger.MessageType.ERROR,"Loading app settings failed with error -- > " + ex.getMessage()); + } + } + + public String getAppSettings(String key) { + if (!key.isEmpty()) { + return appSettings.getProperty(key); + } + return ""; + } +} \ No newline at end of file diff --git a/IncomingCallQuickstart/src/main/java/com/communication/incomingcallquickstart/Controllers/InComingCallController.java b/IncomingCallQuickstart/src/main/java/com/communication/incomingcallquickstart/Controllers/InComingCallController.java new file mode 100644 index 00000000..2c64cc58 --- /dev/null +++ b/IncomingCallQuickstart/src/main/java/com/communication/incomingcallquickstart/Controllers/InComingCallController.java @@ -0,0 +1,26 @@ +package com.communication.incomingcallquickstart.Controllers; + +import java.net.URI; + +import org.springframework.core.io.InputStreamResource; +import org.springframework.http.ResponseEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import java.io.File; +import java.io.FileInputStream; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.GetMapping; + +@RestController +public class InComingCallController { + + @GetMapping("/api/incomingcall") + public static String getCheck() + { + return "OK"; + } +} diff --git a/IncomingCallQuickstart/src/main/java/com/communication/incomingcallquickstart/config.properties b/IncomingCallQuickstart/src/main/java/com/communication/incomingcallquickstart/config.properties new file mode 100644 index 00000000..fe01bf54 --- /dev/null +++ b/IncomingCallQuickstart/src/main/java/com/communication/incomingcallquickstart/config.properties @@ -0,0 +1,30 @@ +# app settings + +#1. Configurations related to Communication Service resource + +# Connection string of Azure Communication Service Resource. +Connectionstring=endpoint=https://fancheacsdev.communication.azure.com/;accesskey=CO4Crd2u1EeMVqvye3HaI4dBBZSIuQZVlMpzVS9UVh4k5OGBzBOhiT3XjfI7itaB5t82M+gtb9W3G8rmgNb4HQ== + +# Phone number provisioned for the ACS resource (in E.164 Format, e.g. +1425XXXYYYY). This is an alternative phone id +SourcePhone=+18332142966 + +# Destination identities to call. +# Format: "Outbound Target1(PhoneNumber), Transfer Target1(PhoneNumber/MRI);Outbound Target2(PhoneNumber), Transfer Target2(PhoneNumber/MRI);Outbound Target3(PhoneNumber), Transfer Target3(PhoneNumber/MRI)" +# For e.g. "+1425XXXAAAA,8:acs:ab12b0ea-85ea-4f83-b0b6-84d90209c7c4_00000009-bce0-da09-54b7-xxxxxxxxxxxx;+1425XXXBBBB,+1425XXXCCCC" +DestinationIdentities=+16314558075,+17788798076 + +MaxRetryCount=2 + +# 2. Configurations related to environment + +# Directory where ngrok.exe is saved. +NgrokExePath="C:\\ProgramData\\chocolatey\\lib\\ngrok\\tools\\" +# Secret for validating incoming request. +SecretPlaceholder=h3llowW0rld + +# Cognitive service key (Optional). +CognitiveServiceKey= +# Cognitive service region (Optional). +CognitiveServiceRegion= +# Custom message that will be translated by Azure Cognitive service (Optional). +CustomMessage=Hello, this is a reminder call. If you would like to speak with a representative Press 1 or 2 if you want to hang up. From a322e79ac4e307c0a4c46ea74c36e0fefb60d28c Mon Sep 17 00:00:00 2001 From: Fang Chen <16232002+fangchen0601@users.noreply.github.com> Date: Mon, 20 Dec 2021 11:17:57 -0800 Subject: [PATCH 04/30] update git ignore --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index dfcfd56f..2c34f568 100644 --- a/.gitignore +++ b/.gitignore @@ -348,3 +348,9 @@ MigrationBackup/ # Ionide (cross platform F# VS Code tools) working folder .ionide/ + +# vscode +.vscode/ + +# Other Tooling # +target From 2759795367ed871b5298c1a96e1a3cdca807d3aa Mon Sep 17 00:00:00 2001 From: Fang Chen <16232002+fangchen0601@users.noreply.github.com> Date: Mon, 20 Dec 2021 11:18:31 -0800 Subject: [PATCH 05/30] new project incomingcallsample --- incomingcallsample/pom.xml | 52 +++++++++++++++++++ .../IncomingcallsampleApplication.java | 13 +++++ 2 files changed, 65 insertions(+) create mode 100644 incomingcallsample/pom.xml create mode 100644 incomingcallsample/src/main/java/com/communication/incomingcallsample/IncomingcallsampleApplication.java diff --git a/incomingcallsample/pom.xml b/incomingcallsample/pom.xml new file mode 100644 index 00000000..71c2c595 --- /dev/null +++ b/incomingcallsample/pom.xml @@ -0,0 +1,52 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.6.1 + + + + com.communication.incomingcallsample + incomingcallsample + 0.0.1-SNAPSHOT + + incomingcallsample + Demo project for IncomingCall + + + UTF-8 + 11 + + + + + junit + junit + 4.11 + test + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/IncomingcallsampleApplication.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/IncomingcallsampleApplication.java new file mode 100644 index 00000000..25aab23c --- /dev/null +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/IncomingcallsampleApplication.java @@ -0,0 +1,13 @@ +package com.communication.incomingcallsample; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class IncomingcallsampleApplication { + + public static void main(String[] args) { + SpringApplication.run(IncomingcallsampleApplication.class, args); + } + +} From 6e458d24777968b3eb597f3ec6624e3b76226e63 Mon Sep 17 00:00:00 2001 From: Fang Chen <16232002+fangchen0601@users.noreply.github.com> Date: Mon, 20 Dec 2021 12:18:11 -0800 Subject: [PATCH 06/30] add sample get controller --- incomingcallsample/pom.xml | 4 ++++ .../Controller/IncomingCallController.java | 21 +++++++++++++++++++ .../incomingcallsample/Greeting.java | 20 ++++++++++++++++++ 3 files changed, 45 insertions(+) create mode 100644 incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java create mode 100644 incomingcallsample/src/main/java/com/communication/incomingcallsample/Greeting.java diff --git a/incomingcallsample/pom.xml b/incomingcallsample/pom.xml index 71c2c595..03e6ddf9 100644 --- a/incomingcallsample/pom.xml +++ b/incomingcallsample/pom.xml @@ -38,6 +38,10 @@ spring-boot-starter-test test + + org.springframework.boot + spring-boot-starter-thymeleaf + diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java new file mode 100644 index 00000000..e81293b2 --- /dev/null +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java @@ -0,0 +1,21 @@ +package com.communication.incomingcallsample.Controller; + +import java.util.concurrent.atomic.AtomicLong; + +import com.communication.incomingcallsample.Greeting; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + + + +public class IncomingCallController { + private static final String template = "Hello, %s!"; + private final AtomicLong counter = new AtomicLong(); + + @GetMapping("/greeting") + public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") String name) { + return new Greeting(counter.incrementAndGet(), String.format(template, name)); + } +} diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/Greeting.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/Greeting.java new file mode 100644 index 00000000..f87e3cc9 --- /dev/null +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/Greeting.java @@ -0,0 +1,20 @@ +package com.communication.incomingcallsample; + +public class Greeting { + + private final long id; + private final String content; + + public Greeting(long id, String content) { + this.id = id; + this.content = content; + } + + public long getId() { + return id; + } + + public String getContent() { + return content; + } +} From 6843f4a1089f574d4cc7fd81bbfe8f71986fc250 Mon Sep 17 00:00:00 2001 From: Fang Chen <16232002+fangchen0601@users.noreply.github.com> Date: Mon, 20 Dec 2021 12:33:24 -0800 Subject: [PATCH 07/30] add sample controller --- incomingcallsample/readm.MD | 5 ++++ .../Controller/IncomingCallController.java | 26 ++++++++++++++++--- .../incomingcallsample/Greeting.java | 20 -------------- .../IncomingcallsampleApplication.java | 15 ++++++++++- 4 files changed, 41 insertions(+), 25 deletions(-) create mode 100644 incomingcallsample/readm.MD delete mode 100644 incomingcallsample/src/main/java/com/communication/incomingcallsample/Greeting.java diff --git a/incomingcallsample/readm.MD b/incomingcallsample/readm.MD new file mode 100644 index 00000000..dd9022de --- /dev/null +++ b/incomingcallsample/readm.MD @@ -0,0 +1,5 @@ +# Start locally +```dotnetcli +mvn clean package +java -jar .\target\incomingcallsample-0.0.1-SNAPSHOT.ja +``` \ No newline at end of file diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java index e81293b2..e232a45c 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java @@ -1,21 +1,39 @@ package com.communication.incomingcallsample.Controller; import java.util.concurrent.atomic.AtomicLong; - -import com.communication.incomingcallsample.Greeting; - import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; - +@RestController public class IncomingCallController { private static final String template = "Hello, %s!"; private final AtomicLong counter = new AtomicLong(); + public class Greeting { + private final long id; + private final String content; + public Greeting(long id, String content) { + this.id = id; + this.content = content; + } + public long getId() { + return id; + } + public String getContent() { + return content; + } + } + @GetMapping("/greeting") public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") String name) { return new Greeting(counter.incrementAndGet(), String.format(template, name)); } + + @GetMapping("/hello") + public String greeting() { + return "OK"; + } } + diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/Greeting.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/Greeting.java deleted file mode 100644 index f87e3cc9..00000000 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/Greeting.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.communication.incomingcallsample; - -public class Greeting { - - private final long id; - private final String content; - - public Greeting(long id, String content) { - this.id = id; - this.content = content; - } - - public long getId() { - return id; - } - - public String getContent() { - return content; - } -} diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/IncomingcallsampleApplication.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/IncomingcallsampleApplication.java index 25aab23c..85338999 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/IncomingcallsampleApplication.java +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/IncomingcallsampleApplication.java @@ -2,12 +2,25 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; +import java.util.*; + +import com.communication.incomingcallsample.Controller.IncomingCallController; + @SpringBootApplication +@ComponentScan(basePackageClasses= IncomingCallController.class) public class IncomingcallsampleApplication { + final static String url = "http://localhost:9008"; + final static String serverPort = "9008"; public static void main(String[] args) { - SpringApplication.run(IncomingcallsampleApplication.class, args); + SpringApplication app = new SpringApplication(IncomingcallsampleApplication.class); + app.setDefaultProperties(Collections + .singletonMap("server.port", serverPort)); + app.run(args); + + } } From 468e2b3d43efacef86a155093668655b23933435 Mon Sep 17 00:00:00 2001 From: Fang Chen <16232002+fangchen0601@users.noreply.github.com> Date: Mon, 20 Dec 2021 12:40:35 -0800 Subject: [PATCH 08/30] update readme --- incomingcallsample/readm.MD | 17 +++++++++++--- .../Controller/IncomingCallController.java | 23 ------------------- 2 files changed, 14 insertions(+), 26 deletions(-) diff --git a/incomingcallsample/readm.MD b/incomingcallsample/readm.MD index dd9022de..1731b131 100644 --- a/incomingcallsample/readm.MD +++ b/incomingcallsample/readm.MD @@ -1,5 +1,16 @@ -# Start locally +## 1.Start web app locally ```dotnetcli mvn clean package -java -jar .\target\incomingcallsample-0.0.1-SNAPSHOT.ja -``` \ No newline at end of file +java -jar .\target\incomingcallsample-0.0.1-SNAPSHOT.jar +``` + +## 2.Start ngrok +```dotnetcli +ngrok http 9008 +``` + +## 3.Try to access from brower +### https://ngrok_url/hello + +example: +https://19ad-75-155-234-140.ngrok.io/hello diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java index e232a45c..ba1012e6 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java @@ -1,6 +1,5 @@ package com.communication.incomingcallsample.Controller; -import java.util.concurrent.atomic.AtomicLong; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -8,28 +7,6 @@ @RestController public class IncomingCallController { - private static final String template = "Hello, %s!"; - private final AtomicLong counter = new AtomicLong(); - - public class Greeting { - private final long id; - private final String content; - public Greeting(long id, String content) { - this.id = id; - this.content = content; - } - public long getId() { - return id; - } - public String getContent() { - return content; - } - } - - @GetMapping("/greeting") - public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") String name) { - return new Greeting(counter.incrementAndGet(), String.format(template, name)); - } @GetMapping("/hello") public String greeting() { From 3da6075127eb429165f456f2e9b76c713748b7c6 Mon Sep 17 00:00:00 2001 From: Fang Chen <16232002+fangchen0601@users.noreply.github.com> Date: Mon, 20 Dec 2021 15:48:24 -0800 Subject: [PATCH 09/30] add handler function for registering acs event --- incomingcallsample/pom.xml | 5 +++ incomingcallsample/readm.MD | 9 ++++ .../Controller/IncomingCallController.java | 45 ++++++++++++++++++- .../IncomingcallsampleApplication.java | 2 - .../utils/ResponseHandler.java | 18 ++++++++ 5 files changed, 75 insertions(+), 4 deletions(-) create mode 100644 incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/ResponseHandler.java diff --git a/incomingcallsample/pom.xml b/incomingcallsample/pom.xml index 03e6ddf9..767d30e7 100644 --- a/incomingcallsample/pom.xml +++ b/incomingcallsample/pom.xml @@ -42,6 +42,11 @@ org.springframework.boot spring-boot-starter-thymeleaf + + com.azure + azure-messaging-eventgrid + 4.6.0 + diff --git a/incomingcallsample/readm.MD b/incomingcallsample/readm.MD index 1731b131..9f5c4f8d 100644 --- a/incomingcallsample/readm.MD +++ b/incomingcallsample/readm.MD @@ -14,3 +14,12 @@ ngrok http 9008 example: https://19ad-75-155-234-140.ngrok.io/hello + +## 4. Register webhook to your ACS resource +```dotnetcli +armclient put "/subscriptions//resourceGroups//providers/Microsoft.Communication/CommunicationServices//providers/Microsoft.EventGrid/eventSubscriptions/IncomingCallEventSub?api-version=2020-06-01" "{'properties':{'destination':{'properties':{'endpointUrl':'https:///OnIncomingCall'},'endpointType':'WebHook'},'filter':{'includedEventTypes': ['Microsoft.Communication.IncomingCall']}}}" -verbose +``` + +## Reference +1. https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/eventgrid/azure-messaging-eventgrid/src/samples/java/com/azure/messaging/eventgrid/samples/DeserializeEventsFromString.java +2. https://www.jamessturtevant.com/posts/Validating-Azure-Event-Grid-WebHook-in-Nodejs/ \ No newline at end of file diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java index ba1012e6..ebe78809 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java @@ -1,16 +1,57 @@ package com.communication.incomingcallsample.Controller; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.azure.messaging.eventgrid.EventGridEvent; +import com.azure.messaging.eventgrid.systemevents.SubscriptionValidationEventData; +import com.azure.messaging.eventgrid.systemevents.SubscriptionValidationResponse; +import com.communication.incomingcallsample.utils.ResponseHandler; @RestController public class IncomingCallController { @GetMapping("/hello") - public String greeting() { - return "OK"; + public ResponseEntity greeting() { + return new ResponseEntity<>("OK", HttpStatus.OK); + } + + @PostMapping(value = "/OnIncomingCall") + public static ResponseEntity onIncomingRequestAsync(@RequestBody(required = false) String data) { + + EventGridEvent eventGridEvent = null; + // parse EventGridEvent + try{ + List eventGridEvents = EventGridEvent.fromString(data); + eventGridEvent = eventGridEvents.get(0); + } + catch(Exception e) { + return new ResponseEntity("Failed to parse EventGridEvent:" + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); + } + if(eventGridEvent == null){ + return new ResponseEntity("Could not get EventGridEvent", HttpStatus.INTERNAL_SERVER_ERROR); + } + + // get event type + String type = eventGridEvent.getEventType(); + if(type.equals("Microsoft.EventGrid.SubscriptionValidationEvent")) { + SubscriptionValidationEventData d = eventGridEvent.getData().toObject(SubscriptionValidationEventData.class); + String validationCode = d.getValidationCode(); + return ResponseEntity.status(HttpStatus.OK).body(Map.of( + "validationResponse", validationCode)); + } else { + return ResponseHandler.generateResponse("unknown EventGridEvent type: " + eventGridEvent.toString() , HttpStatus.BAD_REQUEST, null); + } } } diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/IncomingcallsampleApplication.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/IncomingcallsampleApplication.java index 85338999..c144287b 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/IncomingcallsampleApplication.java +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/IncomingcallsampleApplication.java @@ -19,8 +19,6 @@ public static void main(String[] args) { app.setDefaultProperties(Collections .singletonMap("server.port", serverPort)); app.run(args); - - } } diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/ResponseHandler.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/ResponseHandler.java new file mode 100644 index 00000000..23428863 --- /dev/null +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/ResponseHandler.java @@ -0,0 +1,18 @@ +package com.communication.incomingcallsample.utils; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import java.util.HashMap; +import java.util.Map; + +public class ResponseHandler { + public static ResponseEntity generateResponse(String message, HttpStatus status, Object responseObj) { + Map map = new HashMap(); + map.put("message", message); + map.put("status", status.value()); + map.put("data", responseObj); + + return new ResponseEntity(map,status); + } +} From a25d30cd2cd4fe24e8dbdf733807d02d78863667 Mon Sep 17 00:00:00 2001 From: Fang Chen <16232002+fangchen0601@users.noreply.github.com> Date: Mon, 20 Dec 2021 16:37:40 -0800 Subject: [PATCH 10/30] add logger --- .../Controller/IncomingCallController.java | 10 +++-- .../incomingcallsample/Logger.java | 39 +++++++++++++++++++ 2 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 incomingcallsample/src/main/java/com/communication/incomingcallsample/Logger.java diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java index ebe78809..d164dd19 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java @@ -16,6 +16,7 @@ import com.azure.messaging.eventgrid.EventGridEvent; import com.azure.messaging.eventgrid.systemevents.SubscriptionValidationEventData; import com.azure.messaging.eventgrid.systemevents.SubscriptionValidationResponse; +import com.communication.incomingcallsample.Logger; import com.communication.incomingcallsample.utils.ResponseHandler; @RestController @@ -39,14 +40,17 @@ public static ResponseEntity onIncomingRequestAsync(@RequestBody(required = f return new ResponseEntity("Failed to parse EventGridEvent:" + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); } if(eventGridEvent == null){ - return new ResponseEntity("Could not get EventGridEvent", HttpStatus.INTERNAL_SERVER_ERROR); + return ResponseHandler.generateResponse("Could not get EventGridEvent", HttpStatus.INTERNAL_SERVER_ERROR, null); } + Logger.logEventGridEvent(Logger.MessageType.INFORMATION, eventGridEvent); + // get event type String type = eventGridEvent.getEventType(); if(type.equals("Microsoft.EventGrid.SubscriptionValidationEvent")) { - SubscriptionValidationEventData d = eventGridEvent.getData().toObject(SubscriptionValidationEventData.class); - String validationCode = d.getValidationCode(); + SubscriptionValidationEventData subscriptionValidationEventData = eventGridEvent.getData().toObject( + SubscriptionValidationEventData.class); + String validationCode = subscriptionValidationEventData.getValidationCode(); return ResponseEntity.status(HttpStatus.OK).body(Map.of( "validationResponse", validationCode)); } else { diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/Logger.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/Logger.java new file mode 100644 index 00000000..e9686fb4 --- /dev/null +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/Logger.java @@ -0,0 +1,39 @@ +package com.communication.incomingcallsample; + +import com.azure.messaging.eventgrid.EventGridEvent; + +public class Logger { + //Caution: Logging should be removed/disabled if you want to use this sample in production to avoid exposing sensitive information + public enum MessageType + { + INFORMATION, + ERROR + } + + /// + /// Log message to console + /// + /// Type of the message: Information or Error + /// Message string + public static void logMessage(MessageType messageType, String message) + { + String logMessage; + logMessage = messageType + " " + message; + System.out.println(logMessage); + } + + public static void logEventGridEvent(MessageType messageType, EventGridEvent eventGridEvent){ + String log = new StringBuilder() + .append(messageType + " ") + .append("OnIncomingCall API POST request EventGridEvent---->") + .append(" type: " + eventGridEvent.getEventType()) + .append(";") + .append(" topic: " + eventGridEvent.getTopic()) + .append(";") + .append(" subject: " + eventGridEvent.getSubject()) + .append(";") + .append(" data: " + eventGridEvent.getData()) + .toString(); + System.out.println(log); + } +} From 7adfcc72a110e517e6f9e931bc75daeca5c12e74 Mon Sep 17 00:00:00 2001 From: Fang Chen <16232002+fangchen0601@users.noreply.github.com> Date: Mon, 20 Dec 2021 16:51:48 -0800 Subject: [PATCH 11/30] update readme --- incomingcallsample/{readm.MD => readme.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename incomingcallsample/{readm.MD => readme.md} (100%) diff --git a/incomingcallsample/readm.MD b/incomingcallsample/readme.md similarity index 100% rename from incomingcallsample/readm.MD rename to incomingcallsample/readme.md From ef2121c6f00a6c43733aae3d51f38750e2eacce1 Mon Sep 17 00:00:00 2001 From: Fang Chen <16232002+fangchen0601@users.noreply.github.com> Date: Tue, 21 Dec 2021 17:41:02 -0800 Subject: [PATCH 12/30] incoming call event handler and answercall feature --- incomingcallsample/pom.xml | 15 +++ .../Controller/IncomingCallController.java | 113 ++++++++++++++++-- .../EventHandler/EventAuthHandler.java | 22 ++++ .../incomingcallsample/Logger.java | 39 ------ .../incomingcallsample/config.properties | 22 ++++ .../utils/CallConfiguration.java | 26 ++++ .../utils/ConfigurationManager.java | 45 +++++++ .../utils/IncomingCallHandler.java | 82 +++++++++++++ .../utils/ResponseHandler.java | 2 +- 9 files changed, 313 insertions(+), 53 deletions(-) create mode 100644 incomingcallsample/src/main/java/com/communication/incomingcallsample/EventHandler/EventAuthHandler.java delete mode 100644 incomingcallsample/src/main/java/com/communication/incomingcallsample/Logger.java create mode 100644 incomingcallsample/src/main/java/com/communication/incomingcallsample/config.properties create mode 100644 incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/CallConfiguration.java create mode 100644 incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/ConfigurationManager.java create mode 100644 incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java diff --git a/incomingcallsample/pom.xml b/incomingcallsample/pom.xml index 767d30e7..4da1755f 100644 --- a/incomingcallsample/pom.xml +++ b/incomingcallsample/pom.xml @@ -24,6 +24,11 @@ + com.azure + azure-core + 1.23.0 + + junit junit 4.11 @@ -47,6 +52,16 @@ azure-messaging-eventgrid 4.6.0 + + com.azure + azure-cosmos + 4.18.0 + + + com.azure + azure-communication-callingserver + 1.0.0-beta.4 + diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java index d164dd19..84d3e2da 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java @@ -7,31 +7,90 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import com.azure.communication.callingserver.CallingServerClient; +import com.azure.communication.callingserver.CallingServerClientBuilder; +import com.azure.core.http.netty.NettyAsyncHttpClientBuilder; import com.azure.messaging.eventgrid.EventGridEvent; import com.azure.messaging.eventgrid.systemevents.SubscriptionValidationEventData; -import com.azure.messaging.eventgrid.systemevents.SubscriptionValidationResponse; -import com.communication.incomingcallsample.Logger; -import com.communication.incomingcallsample.utils.ResponseHandler; +import com.communication.incomingcallsample.EventHandler.EventAuthHandler; +import com.communication.incomingcallsample.Log.Logger; +import com.communication.incomingcallsample.Utils.CallConfiguration; +import com.communication.incomingcallsample.Utils.ConfigurationManager; +import com.communication.incomingcallsample.Utils.IncomingCallHandler; +import com.communication.incomingcallsample.Utils.ResponseHandler; @RestController public class IncomingCallController { + private final CallingServerClient callingServerClient; + private CallConfiguration callConfiguration; + private EventAuthHandler eventAuthHandler; + + public IncomingCallController(){ + ConfigurationManager configurationManager = ConfigurationManager.getInstance(); + configurationManager.loadAppSettings(); + this.eventAuthHandler = new EventAuthHandler(configurationManager.getAppSettings("SecretValue")); + this.callConfiguration = CallConfiguration.GetCallConfiguration(configurationManager, "secret=h3llowW0rld"); + + + NettyAsyncHttpClientBuilder httpClientBuilder = new NettyAsyncHttpClientBuilder(); + CallingServerClientBuilder callClientBuilder = new CallingServerClientBuilder().httpClient(httpClientBuilder.build()) + .connectionString(this.callConfiguration.connectionString); + this.callingServerClient = callClientBuilder.buildClient(); + + + /* + HttpPipeline pipeline = new HttpPipelineBuilder() + .policies() + .build(); + this.callingServerClient = new CallingServerClientBuilder() + .pipeline(pipeline) + .connectionString(this.callConfiguration.connectionString) + .buildClient(); + */ + + /* + this.callingServerClient = new CallingServerClientBuilder() + .connectionString(this.callConfiguration.connectionString) + .buildClient(); + */ + + + } @GetMapping("/hello") public ResponseEntity greeting() { return new ResponseEntity<>("OK", HttpStatus.OK); } + @PostMapping(value = "CallingServerAPICallBacks") + public String callingServerAPICallBacks(@RequestBody(required = false) String data, + @RequestParam(value = "secret", required = false) String secretKey) { + //this.eventhandler = EventAuthHandler.getInstance(); + + /// Validating the incoming request by using secret set in app.settings + if (this.eventAuthHandler.authorize(secretKey)) { + // (EventDispatcher.getInstance()).processNotification(data); + } else { + Logger.logMessage(Logger.MessageType.ERROR, "Unauthorized Request"); + } + + return "OK"; + } + @PostMapping(value = "/OnIncomingCall") - public static ResponseEntity onIncomingRequestAsync(@RequestBody(required = false) String data) { + public ResponseEntity onIncomingRequestAsync(@RequestBody(required = false) String data) { - EventGridEvent eventGridEvent = null; // parse EventGridEvent + EventGridEvent eventGridEvent = null; try{ List eventGridEvents = EventGridEvent.fromString(data); eventGridEvent = eventGridEvents.get(0); @@ -45,17 +104,45 @@ public static ResponseEntity onIncomingRequestAsync(@RequestBody(required = f Logger.logEventGridEvent(Logger.MessageType.INFORMATION, eventGridEvent); - // get event type String type = eventGridEvent.getEventType(); if(type.equals("Microsoft.EventGrid.SubscriptionValidationEvent")) { - SubscriptionValidationEventData subscriptionValidationEventData = eventGridEvent.getData().toObject( - SubscriptionValidationEventData.class); - String validationCode = subscriptionValidationEventData.getValidationCode(); - return ResponseEntity.status(HttpStatus.OK).body(Map.of( - "validationResponse", validationCode)); + return getRegisterEventGridResponse(eventGridEvent); + } else if(type.equals("Microsoft.Communication.IncomingCall")) { + return answerCall(data); } else { return ResponseHandler.generateResponse("unknown EventGridEvent type: " + eventGridEvent.toString() , HttpStatus.BAD_REQUEST, null); } } + + private static ResponseEntity getRegisterEventGridResponse(EventGridEvent eventGridEvent){ + SubscriptionValidationEventData subscriptionValidationEventData = eventGridEvent.getData().toObject( + SubscriptionValidationEventData.class); + String validationCode = subscriptionValidationEventData.getValidationCode(); + Logger.logMessage(Logger.MessageType.INFORMATION, "Registered ACS resource Event Grid."); + return ResponseEntity.status(HttpStatus.OK).body(Map.of( + "validationResponse", validationCode)); + } + + private ResponseEntity answerCall(String data) { + try { + String incomingCallContext = data.split("\"incomingCallContext\":\"")[1].split("\"}")[0]; + + Logger.logMessage(Logger.MessageType.INFORMATION, "Microsoft.Communication.IncomingCall call context: " + incomingCallContext); + + ExecutorService executorService = Executors.newCachedThreadPool(); + Set> tasks = new HashSet<>(); + tasks.add(() -> { + new IncomingCallHandler(this.callingServerClient, this.callConfiguration).report(incomingCallContext); + return true; + }); + executorService.invokeAll(tasks); + executorService.shutdown(); + } catch(Exception e) { + String message = "Fails in OnIncomingCall ---> " + e.getMessage(); + Logger.logMessage(Logger.MessageType.ERROR, message); + return ResponseHandler.generateResponse(message, HttpStatus.INTERNAL_SERVER_ERROR, null); + } + return ResponseHandler.generateResponse("answer call done", HttpStatus.OK, null); + } } diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/EventHandler/EventAuthHandler.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/EventHandler/EventAuthHandler.java new file mode 100644 index 00000000..4ae2a930 --- /dev/null +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/EventHandler/EventAuthHandler.java @@ -0,0 +1,22 @@ +package com.communication.incomingcallsample.EventHandler; + +public class EventAuthHandler { + private final String secretValue; + private final String secreteKey = "secret"; + + public EventAuthHandler(String secretValue){ + this.secretValue = secretValue; + } + + public String GetSecretQuerystring(){ + return this.secreteKey + "=" + secretValue; + } + + public boolean authorize(String query){ + if(query==null || query.isEmpty()) { + return false; + } + + return query.equals(this.secretValue); + } +} diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/Logger.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/Logger.java deleted file mode 100644 index e9686fb4..00000000 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/Logger.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.communication.incomingcallsample; - -import com.azure.messaging.eventgrid.EventGridEvent; - -public class Logger { - //Caution: Logging should be removed/disabled if you want to use this sample in production to avoid exposing sensitive information - public enum MessageType - { - INFORMATION, - ERROR - } - - /// - /// Log message to console - /// - /// Type of the message: Information or Error - /// Message string - public static void logMessage(MessageType messageType, String message) - { - String logMessage; - logMessage = messageType + " " + message; - System.out.println(logMessage); - } - - public static void logEventGridEvent(MessageType messageType, EventGridEvent eventGridEvent){ - String log = new StringBuilder() - .append(messageType + " ") - .append("OnIncomingCall API POST request EventGridEvent---->") - .append(" type: " + eventGridEvent.getEventType()) - .append(";") - .append(" topic: " + eventGridEvent.getTopic()) - .append(";") - .append(" subject: " + eventGridEvent.getSubject()) - .append(";") - .append(" data: " + eventGridEvent.getData()) - .toString(); - System.out.println(log); - } -} diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/config.properties b/incomingcallsample/src/main/java/com/communication/incomingcallsample/config.properties new file mode 100644 index 00000000..4304cc4a --- /dev/null +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/config.properties @@ -0,0 +1,22 @@ +# app settings + +# Connection string of Azure Communication Service Resource. +Connectionstring=%Connectionstring% + +# url of the deployed API +# For e.g. "https://4bf7-75-155-234-140.ngrok.io" +AppCallBackUri=%AppCallBackUri% + +# public url of wav audio +# For e.g. "https://acstestapp1.azurewebsites.net/audio/bot-hold-music-2.wav" +AudiFileUri=%AudiFileUri% + +# Destination identitie to transfer the call to. +# Phone number provisioned for the ACS resource (in E.164 Format, e.g. +1425XXXYYYY) +# or MRI(e.g. 8:acs:ab12b0ea-85ea-4f83-b0b6-84d90209c7c4_00000009-bce0-da09-54b7-xxxxxxxxxxxx) +TargetParticipant=%TargetParticipant% + +# Query string for callback URL +SecretValue=%SecretValue% + + diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/CallConfiguration.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/CallConfiguration.java new file mode 100644 index 00000000..87713376 --- /dev/null +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/CallConfiguration.java @@ -0,0 +1,26 @@ +package com.communication.incomingcallsample.Utils; + +public class CallConfiguration { + public String connectionString; + public String appBaseUrl; + public String appCallbackUrl; + public String audioFileUrl; + public String targetParticipant; + + public CallConfiguration(String connectionString, String appBaseUrl, String audioFileUrl, String targetParticipant, String queryString) { + this.connectionString = connectionString; + this.appBaseUrl = appBaseUrl; + this.appCallbackUrl = this.appBaseUrl + "/CallingServerAPICallBacks?" + queryString; + this.audioFileUrl = audioFileUrl; + this.targetParticipant = targetParticipant; + } + + public static CallConfiguration GetCallConfiguration(ConfigurationManager configurationManager, String queryString) { + return new CallConfiguration( + configurationManager.getAppSettings("Connectionstring"), + configurationManager.getAppSettings("AppCallBackUri"), + configurationManager.getAppSettings("AudiFileUri"), + configurationManager.getAppSettings("TargetParticipant"), + queryString); + } +} diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/ConfigurationManager.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/ConfigurationManager.java new file mode 100644 index 00000000..27669d4a --- /dev/null +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/ConfigurationManager.java @@ -0,0 +1,45 @@ +package com.communication.incomingcallsample.Utils; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.Properties; + +import com.communication.incomingcallsample.Log.Logger; + +public class ConfigurationManager { + private static ConfigurationManager configurationManager = null; + private final Properties appSettings = new Properties(); + + private ConfigurationManager() { + } + + // static method to create instance of ConfigurationManager class + public static ConfigurationManager getInstance() { + if (configurationManager == null) { + configurationManager = new ConfigurationManager(); + } + return configurationManager; + } + + public void loadAppSettings() { + try { + File configFile = new File("src/main/java/com/communication/incomingcallsample/config.properties"); + FileReader reader = new FileReader(configFile); + appSettings.load(reader); + reader.close(); + } catch (FileNotFoundException ex) { + Logger.logMessage(Logger.MessageType.ERROR,"Loading app settings failed with error -- > " + ex.getMessage()); + } catch (IOException ex) { + Logger.logMessage(Logger.MessageType.ERROR,"Loading app settings failed with error -- > " + ex.getMessage()); + } + } + + public String getAppSettings(String key) { + if (!key.isEmpty()) { + return appSettings.getProperty(key); + } + return ""; + } +} diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java new file mode 100644 index 00000000..88e39fe4 --- /dev/null +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java @@ -0,0 +1,82 @@ +package com.communication.incomingcallsample.Utils; + +import com.azure.communication.callingserver.CallConnection; +import com.azure.communication.callingserver.CallingServerClient; +import com.azure.communication.callingserver.models.AnswerCallOptions; +import com.azure.communication.callingserver.models.CallMediaType; +import com.azure.communication.callingserver.models.CallingEventSubscriptionType; +import com.azure.core.http.HttpHeader; +import com.azure.core.http.rest.Response; +import com.azure.cosmos.implementation.changefeed.CancellationToken; +import com.azure.cosmos.implementation.changefeed.CancellationTokenSource; +import com.communication.incomingcallsample.Log.Logger; + +import java.net.URI; +import java.util.ArrayList; +import java.util.concurrent.CompletableFuture; + +public class IncomingCallHandler { + private final CallingServerClient callingServerClient; + private final CallConfiguration callConfiguration; + private CallConnection callConnection = null; + private CancellationTokenSource reportCancellationTokenSource; + private CancellationToken reportCancellationToken; + private String targetParticipant; + + private CompletableFuture callConnectedTask; + private CompletableFuture playAudioCompletedTask; + private CompletableFuture callTerminatedTask; + private CompletableFuture toneReceivedCompleteTask; + private CompletableFuture addParticipantCompleteTask; + + public IncomingCallHandler(CallingServerClient callingServerClient, CallConfiguration callConfiguration) { + this.callingServerClient = callingServerClient; + this.callConfiguration = callConfiguration; + } + + public void report(String incomingCallContext){ + this.reportCancellationTokenSource = new CancellationTokenSource(); + this.reportCancellationToken = reportCancellationTokenSource.getToken(); + + try { + //answer the call + AnswerCallOptions answerCallOptions = new AnswerCallOptions( + new URI(this.callConfiguration.appCallbackUrl), + new ArrayList() { + { + add(CallMediaType.AUDIO); + } + }, + new ArrayList() { + { + add(CallingEventSubscriptionType.PARTICIPANTS_UPDATED); + add(CallingEventSubscriptionType.TONE_RECEIVED); + } + } + ); + Logger.logMessage(Logger.MessageType.INFORMATION, "Answering call..."); + Response response = this.callingServerClient.answerCallWithResponse(incomingCallContext, answerCallOptions, null); + callConnection = response.getValue(); + Logger.logMessage(Logger.MessageType.INFORMATION, "AnswerCallAsync call -----> callConnectionId: " + callConnection.getCallConnectionId()); + Logger.logMessage(Logger.MessageType.INFORMATION, "AnswerCallAsync Response -----> " + getResponse(response)); + // to do: registerToCallStateChangeEvent(callConnection.getCallConnectionId()); + callConnectedTask.get(); + + } catch (Exception ex) + { + Logger.logMessage(Logger.MessageType.ERROR, "Call ended unexpectedly, reason: " + ex.getMessage()); + } + } + + private String getResponse(Response response) + { + StringBuilder responseString; + responseString = new StringBuilder("StatusCode: " + response.getStatusCode() + ", Headers: { "); + + for (HttpHeader header : response.getHeaders()) { + responseString.append(header.getName()).append(":").append(header.getValue()).append(", "); + } + responseString.append("} "); + return responseString.toString(); + } +} diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/ResponseHandler.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/ResponseHandler.java index 23428863..29d7ef0e 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/ResponseHandler.java +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/ResponseHandler.java @@ -1,4 +1,4 @@ -package com.communication.incomingcallsample.utils; +package com.communication.incomingcallsample.Utils; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; From 3b6cc5bee0ff58e60cef4e44cfee75bc76c9e4db Mon Sep 17 00:00:00 2001 From: Fang Chen <16232002+fangchen0601@users.noreply.github.com> Date: Wed, 22 Dec 2021 13:21:13 -0800 Subject: [PATCH 13/30] add EventDispatcher and callback for answering call --- .../Controller/IncomingCallController.java | 31 +----- .../EventHandler/EventDispatcher.java | 103 ++++++++++++++++++ .../EventHandler/NotificationCallback.java | 7 ++ .../utils/ConfigurationManager.java | 1 + .../utils/IncomingCallHandler.java | 85 +++++++++++---- 5 files changed, 179 insertions(+), 48 deletions(-) create mode 100644 incomingcallsample/src/main/java/com/communication/incomingcallsample/EventHandler/EventDispatcher.java create mode 100644 incomingcallsample/src/main/java/com/communication/incomingcallsample/EventHandler/NotificationCallback.java diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java index 84d3e2da..5cfe086f 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java @@ -18,10 +18,10 @@ import com.azure.communication.callingserver.CallingServerClient; import com.azure.communication.callingserver.CallingServerClientBuilder; -import com.azure.core.http.netty.NettyAsyncHttpClientBuilder; import com.azure.messaging.eventgrid.EventGridEvent; import com.azure.messaging.eventgrid.systemevents.SubscriptionValidationEventData; import com.communication.incomingcallsample.EventHandler.EventAuthHandler; +import com.communication.incomingcallsample.EventHandler.EventDispatcher; import com.communication.incomingcallsample.Log.Logger; import com.communication.incomingcallsample.Utils.CallConfiguration; import com.communication.incomingcallsample.Utils.ConfigurationManager; @@ -36,34 +36,12 @@ public class IncomingCallController { public IncomingCallController(){ ConfigurationManager configurationManager = ConfigurationManager.getInstance(); - configurationManager.loadAppSettings(); this.eventAuthHandler = new EventAuthHandler(configurationManager.getAppSettings("SecretValue")); - this.callConfiguration = CallConfiguration.GetCallConfiguration(configurationManager, "secret=h3llowW0rld"); + this.callConfiguration = CallConfiguration.GetCallConfiguration(configurationManager, this.eventAuthHandler.GetSecretQuerystring()); - - NettyAsyncHttpClientBuilder httpClientBuilder = new NettyAsyncHttpClientBuilder(); - CallingServerClientBuilder callClientBuilder = new CallingServerClientBuilder().httpClient(httpClientBuilder.build()) - .connectionString(this.callConfiguration.connectionString); - this.callingServerClient = callClientBuilder.buildClient(); - - - /* - HttpPipeline pipeline = new HttpPipelineBuilder() - .policies() - .build(); - this.callingServerClient = new CallingServerClientBuilder() - .pipeline(pipeline) - .connectionString(this.callConfiguration.connectionString) - .buildClient(); - */ - - /* this.callingServerClient = new CallingServerClientBuilder() .connectionString(this.callConfiguration.connectionString) .buildClient(); - */ - - } @GetMapping("/hello") @@ -74,11 +52,10 @@ public ResponseEntity greeting() { @PostMapping(value = "CallingServerAPICallBacks") public String callingServerAPICallBacks(@RequestBody(required = false) String data, @RequestParam(value = "secret", required = false) String secretKey) { - //this.eventhandler = EventAuthHandler.getInstance(); - /// Validating the incoming request by using secret set in app.settings + // Validating the incoming request by using secret set in config.properties if (this.eventAuthHandler.authorize(secretKey)) { - // (EventDispatcher.getInstance()).processNotification(data); + EventDispatcher.getInstance().processNotification(data); } else { Logger.logMessage(Logger.MessageType.ERROR, "Unauthorized Request"); } diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/EventHandler/EventDispatcher.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/EventHandler/EventDispatcher.java new file mode 100644 index 00000000..9efa97ab --- /dev/null +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/EventHandler/EventDispatcher.java @@ -0,0 +1,103 @@ +package com.communication.incomingcallsample.EventHandler; + +import java.util.Hashtable; +import java.util.List; + +import com.azure.communication.callingserver.models.events.AddParticipantResultEvent; +import com.azure.communication.callingserver.models.events.CallConnectionStateChangedEvent; +import com.azure.communication.callingserver.models.events.CallingServerEventBase; +import com.azure.communication.callingserver.models.events.CallingServerEventType; +import com.azure.communication.callingserver.models.events.PlayAudioResultEvent; +import com.azure.communication.callingserver.models.events.ToneReceivedEvent; +import com.azure.core.models.CloudEvent; +import com.azure.core.util.BinaryData; +import com.communication.incomingcallsample.Log.Logger; + +public class EventDispatcher { + private static EventDispatcher instance = null; + private final Hashtable notificationCallbacks; + + EventDispatcher() { + this.notificationCallbacks = new Hashtable<>(); + } + + /// + /// Get instance of EventDispatcher + /// + public static EventDispatcher getInstance() { + if (instance == null) { + instance = new EventDispatcher(); + } + return instance; + } + + public boolean subscribe(String eventType, String eventKey, NotificationCallback notificationCallback) { + String eventId = buildEventKey(eventType, eventKey); + synchronized (this) { + return (notificationCallbacks.put(eventId, notificationCallback) == null); + } + } + + public void unsubscribe(String eventType, String eventKey) { + String eventId = buildEventKey(eventType, eventKey); + synchronized (this) { + notificationCallbacks.remove(eventId); + } + } + + private String buildEventKey(String eventType, String eventKey) { + return (eventType + "-" + eventKey); + } + + public void processNotification(String request) { + CallingServerEventBase callEvent = this.extractEvent(request); + if (callEvent != null) { + synchronized (this) { + final NotificationCallback notificationCallback = notificationCallbacks.get(getEventKey(callEvent)); + if (notificationCallback != null) { + new Thread(() -> notificationCallback.callback(callEvent)).start(); + } + } + } + } + + private CallingServerEventBase extractEvent(String content) { + try { + List cloudEvents = CloudEvent.fromString(content); + CloudEvent cloudEvent = cloudEvents.get(0); + BinaryData eventData = cloudEvent.getData(); + + if (cloudEvent.getType().equals(CallingServerEventType.CALL_CONNECTION_STATE_CHANGED_EVENT.toString())) { + return CallConnectionStateChangedEvent.deserialize(eventData); + } else if (cloudEvent.getType().equals(CallingServerEventType.TONE_RECEIVED_EVENT.toString())) { + return ToneReceivedEvent.deserialize(eventData); + } else if (cloudEvent.getType().equals(CallingServerEventType.PLAY_AUDIO_RESULT_EVENT.toString())) { + return PlayAudioResultEvent.deserialize(eventData); + } else if (cloudEvent.getType().equals(CallingServerEventType.ADD_PARTICIPANT_RESULT_EVENT.toString())) { + return AddParticipantResultEvent.deserialize(eventData); + } + } catch (Exception ex) { + System.out.println("Failed to parse request content Exception: " + ex.getMessage()); + } + + return null; + } + + private String getEventKey(CallingServerEventBase callEventBase) { + if (callEventBase.getClass() == CallConnectionStateChangedEvent.class) { + String callLegId = ((CallConnectionStateChangedEvent) callEventBase).getCallConnectionId(); + return buildEventKey(CallingServerEventType.CALL_CONNECTION_STATE_CHANGED_EVENT.toString(), callLegId); + } else if (callEventBase.getClass() == ToneReceivedEvent.class) { + String callLegId = ((ToneReceivedEvent) callEventBase).getCallConnectionId(); + return buildEventKey(CallingServerEventType.TONE_RECEIVED_EVENT.toString(), callLegId); + } else if (callEventBase.getClass() == PlayAudioResultEvent.class) { + String operationContext = ((PlayAudioResultEvent) callEventBase).getOperationContext(); + return buildEventKey(CallingServerEventType.PLAY_AUDIO_RESULT_EVENT.toString(), operationContext); + } else if (callEventBase.getClass() == AddParticipantResultEvent.class) { + String operationContext = ((AddParticipantResultEvent) callEventBase).getOperationContext(); + return buildEventKey(CallingServerEventType.ADD_PARTICIPANT_RESULT_EVENT.toString(), operationContext); + } + + return null; + } +} diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/EventHandler/NotificationCallback.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/EventHandler/NotificationCallback.java new file mode 100644 index 00000000..f45cc383 --- /dev/null +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/EventHandler/NotificationCallback.java @@ -0,0 +1,7 @@ +package com.communication.incomingcallsample.EventHandler; + +import com.azure.communication.callingserver.models.events.CallingServerEventBase; + +public interface NotificationCallback { + void callback(CallingServerEventBase callEvent); +} diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/ConfigurationManager.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/ConfigurationManager.java index 27669d4a..6e8f1776 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/ConfigurationManager.java +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/ConfigurationManager.java @@ -13,6 +13,7 @@ public class ConfigurationManager { private final Properties appSettings = new Properties(); private ConfigurationManager() { + loadAppSettings(); } // static method to create instance of ConfigurationManager class diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java index 88e39fe4..3e800586 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java @@ -3,12 +3,17 @@ import com.azure.communication.callingserver.CallConnection; import com.azure.communication.callingserver.CallingServerClient; import com.azure.communication.callingserver.models.AnswerCallOptions; +import com.azure.communication.callingserver.models.CallConnectionState; import com.azure.communication.callingserver.models.CallMediaType; import com.azure.communication.callingserver.models.CallingEventSubscriptionType; +import com.azure.communication.callingserver.models.events.CallConnectionStateChangedEvent; +import com.azure.communication.callingserver.models.events.CallingServerEventType; import com.azure.core.http.HttpHeader; import com.azure.core.http.rest.Response; import com.azure.cosmos.implementation.changefeed.CancellationToken; import com.azure.cosmos.implementation.changefeed.CancellationTokenSource; +import com.communication.incomingcallsample.EventHandler.EventDispatcher; +import com.communication.incomingcallsample.EventHandler.NotificationCallback; import com.communication.incomingcallsample.Log.Logger; import java.net.URI; @@ -40,28 +45,14 @@ public void report(String incomingCallContext){ try { //answer the call - AnswerCallOptions answerCallOptions = new AnswerCallOptions( - new URI(this.callConfiguration.appCallbackUrl), - new ArrayList() { - { - add(CallMediaType.AUDIO); - } - }, - new ArrayList() { - { - add(CallingEventSubscriptionType.PARTICIPANTS_UPDATED); - add(CallingEventSubscriptionType.TONE_RECEIVED); - } - } - ); - Logger.logMessage(Logger.MessageType.INFORMATION, "Answering call..."); - Response response = this.callingServerClient.answerCallWithResponse(incomingCallContext, answerCallOptions, null); - callConnection = response.getValue(); - Logger.logMessage(Logger.MessageType.INFORMATION, "AnswerCallAsync call -----> callConnectionId: " + callConnection.getCallConnectionId()); - Logger.logMessage(Logger.MessageType.INFORMATION, "AnswerCallAsync Response -----> " + getResponse(response)); - // to do: registerToCallStateChangeEvent(callConnection.getCallConnectionId()); - callConnectedTask.get(); + answerCall(incomingCallContext); + + // to do: play audio + + // Wait for the call to terminate + callTerminatedTask.get(); + Logger.logMessage(Logger.MessageType.INFORMATION, "call terminated."); } catch (Exception ex) { Logger.logMessage(Logger.MessageType.ERROR, "Call ended unexpectedly, reason: " + ex.getMessage()); @@ -79,4 +70,56 @@ private String getResponse(Response response) responseString.append("} "); return responseString.toString(); } + + private void answerCall(String incomingCallContext) throws Exception{ + //answer the call + AnswerCallOptions answerCallOptions = new AnswerCallOptions( + new URI(this.callConfiguration.appCallbackUrl), + new ArrayList() { + { + add(CallMediaType.AUDIO); + } + }, + new ArrayList() { + { + add(CallingEventSubscriptionType.PARTICIPANTS_UPDATED); + add(CallingEventSubscriptionType.TONE_RECEIVED); + } + } + ); + Logger.logMessage(Logger.MessageType.INFORMATION, "Answering call..."); + Response response = this.callingServerClient.answerCallWithResponse(incomingCallContext, answerCallOptions, null); + callConnection = response.getValue(); + String callConnectionId = callConnection.getCallConnectionId(); + Logger.logMessage(Logger.MessageType.INFORMATION, "AnswerCallAsync Response -----> " + getResponse(response)); + Logger.logMessage(Logger.MessageType.INFORMATION, "AnswerCallAsync call -----> callConnectionId: " + callConnectionId); + + registerToCallStateChangeEvent(callConnectionId); + callConnectedTask.get(); + } + + private void registerToCallStateChangeEvent(String callConnectionId) { + callConnectedTask = new CompletableFuture<>(); + callTerminatedTask = new CompletableFuture<>(); + // Set the callback method + NotificationCallback callStateChangeNotificaiton = (callEvent) -> { + CallConnectionStateChangedEvent callStateChanged = (CallConnectionStateChangedEvent) callEvent; + + Logger.logMessage( + Logger.MessageType.INFORMATION, + "Call State changed to -- > " + callStateChanged.getCallConnectionState() + " callConnectionId: " + callConnectionId); + + if (callStateChanged.getCallConnectionState().equals(CallConnectionState.CONNECTED)) { + callConnectedTask.complete(true); + } else if (callStateChanged.getCallConnectionState().equals(CallConnectionState.DISCONNECTED)) { + EventDispatcher.getInstance() + .unsubscribe(CallingServerEventType.CALL_CONNECTION_STATE_CHANGED_EVENT.toString(), callConnectionId); + reportCancellationTokenSource.cancel(); + callTerminatedTask.complete(true); + } + }; + // Subscribe to the event + EventDispatcher.getInstance().subscribe(CallingServerEventType.CALL_CONNECTION_STATE_CHANGED_EVENT.toString(), + callConnectionId, callStateChangeNotificaiton); + } } From 25ab1070a338a5cea81307b7b4163446624b7bfb Mon Sep 17 00:00:00 2001 From: Fang Chen <16232002+fangchen0601@users.noreply.github.com> Date: Thu, 23 Dec 2021 16:05:35 -0800 Subject: [PATCH 14/30] add feature to play audio --- .../Controller/IncomingCallController.java | 18 +-- .../utils/IncomingCallHandler.java | 110 +++++++++++++++--- 2 files changed, 100 insertions(+), 28 deletions(-) diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java index 5cfe086f..65d58399 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java @@ -44,11 +44,6 @@ public IncomingCallController(){ .buildClient(); } - @GetMapping("/hello") - public ResponseEntity greeting() { - return new ResponseEntity<>("OK", HttpStatus.OK); - } - @PostMapping(value = "CallingServerAPICallBacks") public String callingServerAPICallBacks(@RequestBody(required = false) String data, @RequestParam(value = "secret", required = false) String secretKey) { @@ -85,7 +80,7 @@ public ResponseEntity onIncomingRequestAsync(@RequestBody(required = false) S if(type.equals("Microsoft.EventGrid.SubscriptionValidationEvent")) { return getRegisterEventGridResponse(eventGridEvent); } else if(type.equals("Microsoft.Communication.IncomingCall")) { - return answerCall(data); + return handleIncomingCall(data); } else { return ResponseHandler.generateResponse("unknown EventGridEvent type: " + eventGridEvent.toString() , HttpStatus.BAD_REQUEST, null); } @@ -100,20 +95,13 @@ private static ResponseEntity getRegisterEventGridResponse(EventGridEvent eve "validationResponse", validationCode)); } - private ResponseEntity answerCall(String data) { + private ResponseEntity handleIncomingCall(String data) { try { String incomingCallContext = data.split("\"incomingCallContext\":\"")[1].split("\"}")[0]; Logger.logMessage(Logger.MessageType.INFORMATION, "Microsoft.Communication.IncomingCall call context: " + incomingCallContext); - ExecutorService executorService = Executors.newCachedThreadPool(); - Set> tasks = new HashSet<>(); - tasks.add(() -> { - new IncomingCallHandler(this.callingServerClient, this.callConfiguration).report(incomingCallContext); - return true; - }); - executorService.invokeAll(tasks); - executorService.shutdown(); + new IncomingCallHandler(this.callingServerClient, this.callConfiguration).report(incomingCallContext); } catch(Exception e) { String message = "Fails in OnIncomingCall ---> " + e.getMessage(); Logger.logMessage(Logger.MessageType.ERROR, message); diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java index 3e800586..47b25d49 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java @@ -6,8 +6,12 @@ import com.azure.communication.callingserver.models.CallConnectionState; import com.azure.communication.callingserver.models.CallMediaType; import com.azure.communication.callingserver.models.CallingEventSubscriptionType; +import com.azure.communication.callingserver.models.CallingOperationStatus; +import com.azure.communication.callingserver.models.PlayAudioOptions; +import com.azure.communication.callingserver.models.PlayAudioResult; import com.azure.communication.callingserver.models.events.CallConnectionStateChangedEvent; import com.azure.communication.callingserver.models.events.CallingServerEventType; +import com.azure.communication.callingserver.models.events.PlayAudioResultEvent; import com.azure.core.http.HttpHeader; import com.azure.core.http.rest.Response; import com.azure.cosmos.implementation.changefeed.CancellationToken; @@ -18,7 +22,10 @@ import java.net.URI; import java.util.ArrayList; +import java.util.UUID; +import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; public class IncomingCallHandler { private final CallingServerClient callingServerClient; @@ -41,16 +48,22 @@ public IncomingCallHandler(CallingServerClient callingServerClient, CallConfigur public void report(String incomingCallContext){ this.reportCancellationTokenSource = new CancellationTokenSource(); - this.reportCancellationToken = reportCancellationTokenSource.getToken(); + this.reportCancellationToken = this.reportCancellationTokenSource.getToken(); try { //answer the call - answerCall(incomingCallContext); + String callConnectionId = answerCall(incomingCallContext); + registerToCallStateChangeEvent(callConnectionId); + this.callConnectedTask.get(); + + // to do: registerToDtmfResultEvent // to do: play audio + playAudio(); + Boolean playAudioCompleted = this.playAudioCompletedTask.get(); // Wait for the call to terminate - callTerminatedTask.get(); + this.callTerminatedTask.get(); Logger.logMessage(Logger.MessageType.INFORMATION, "call terminated."); } catch (Exception ex) @@ -71,7 +84,7 @@ private String getResponse(Response response) return responseString.toString(); } - private void answerCall(String incomingCallContext) throws Exception{ + private String answerCall(String incomingCallContext) throws Exception{ //answer the call AnswerCallOptions answerCallOptions = new AnswerCallOptions( new URI(this.callConfiguration.appCallbackUrl), @@ -89,18 +102,17 @@ private void answerCall(String incomingCallContext) throws Exception{ ); Logger.logMessage(Logger.MessageType.INFORMATION, "Answering call..."); Response response = this.callingServerClient.answerCallWithResponse(incomingCallContext, answerCallOptions, null); - callConnection = response.getValue(); - String callConnectionId = callConnection.getCallConnectionId(); + this.callConnection = response.getValue(); + String callConnectionId = this.callConnection.getCallConnectionId(); Logger.logMessage(Logger.MessageType.INFORMATION, "AnswerCallAsync Response -----> " + getResponse(response)); Logger.logMessage(Logger.MessageType.INFORMATION, "AnswerCallAsync call -----> callConnectionId: " + callConnectionId); - registerToCallStateChangeEvent(callConnectionId); - callConnectedTask.get(); + return callConnectionId; } private void registerToCallStateChangeEvent(String callConnectionId) { - callConnectedTask = new CompletableFuture<>(); - callTerminatedTask = new CompletableFuture<>(); + this.callConnectedTask = new CompletableFuture<>(); + this.callTerminatedTask = new CompletableFuture<>(); // Set the callback method NotificationCallback callStateChangeNotificaiton = (callEvent) -> { CallConnectionStateChangedEvent callStateChanged = (CallConnectionStateChangedEvent) callEvent; @@ -110,16 +122,88 @@ private void registerToCallStateChangeEvent(String callConnectionId) { "Call State changed to -- > " + callStateChanged.getCallConnectionState() + " callConnectionId: " + callConnectionId); if (callStateChanged.getCallConnectionState().equals(CallConnectionState.CONNECTED)) { - callConnectedTask.complete(true); + this.callConnectedTask.complete(true); } else if (callStateChanged.getCallConnectionState().equals(CallConnectionState.DISCONNECTED)) { EventDispatcher.getInstance() .unsubscribe(CallingServerEventType.CALL_CONNECTION_STATE_CHANGED_EVENT.toString(), callConnectionId); - reportCancellationTokenSource.cancel(); - callTerminatedTask.complete(true); + this.reportCancellationTokenSource.cancel(); + this.callTerminatedTask.complete(true); } }; // Subscribe to the event EventDispatcher.getInstance().subscribe(CallingServerEventType.CALL_CONNECTION_STATE_CHANGED_EVENT.toString(), callConnectionId, callStateChangeNotificaiton); } + + private void playAudio(){ + if (this.reportCancellationToken.isCancellationRequested()) { + Logger.logMessage(Logger.MessageType.INFORMATION, "Cancellation request, PlayAudio will not be performed"); + return; + } + + try{ + PlayAudioOptions playAudioOptions = new PlayAudioOptions(); + playAudioOptions.setLoop(true); + playAudioOptions.setAudioFileId(UUID.randomUUID().toString()); + playAudioOptions.setOperationContext(UUID.randomUUID().toString()); + Logger.logMessage(Logger.MessageType.INFORMATION, "Performing PlayAudio operation"); + Response playAudioResponse = this.callConnection.playAudioWithResponse(new URI(this.callConfiguration.audioFileUrl), playAudioOptions, null); + PlayAudioResult response = playAudioResponse.getValue(); + Logger.logMessage(Logger.MessageType.INFORMATION, "playAudioWithResponse -- > " + getResponse(playAudioResponse) + + ", Id: " + response.getOperationId() + ", OperationContext: " + response.getOperationContext() + ", OperationStatus: " + + response.getStatus().toString()); + + if(response.getStatus().equals(CallingOperationStatus.RUNNING)) { + Logger.logMessage(Logger.MessageType.INFORMATION, "Audio is playing, registering PlayAudioResultEvent"); + + // listen to play audio events + registerToPlayAudioResultEvent(response.getOperationContext()); + + CompletableFuture maxWait = CompletableFuture.supplyAsync(() -> { + try { + TimeUnit.SECONDS.sleep(5); + } catch (InterruptedException ex) { + Logger.logMessage(Logger.MessageType.ERROR, " -- > " + ex.getMessage()); + } + return false; + }); + + CompletableFuture completedTask = CompletableFuture.anyOf(playAudioCompletedTask, maxWait); + if (completedTask.get() != playAudioCompletedTask.get()) { + Logger.logMessage(Logger.MessageType.INFORMATION, "No response from user in 30 sec, initiating hangup"); + playAudioCompletedTask.complete(false); + toneReceivedCompleteTask.complete(false); + } + + } + } catch (CancellationException e) { + Logger.logMessage(Logger.MessageType.INFORMATION, "Play audio operation cancelled"); + } catch (Exception ex) { + if (playAudioCompletedTask.isCancelled()) { + Logger.logMessage(Logger.MessageType.INFORMATION, "Play audio operation cancelled"); + } else { + Logger.logMessage(Logger.MessageType.INFORMATION, "Failure occured while playing audio on the call. Exception: " + ex.getMessage()); + } + } + } + + private void registerToPlayAudioResultEvent(String operationContext) { + playAudioCompletedTask = new CompletableFuture<>(); + NotificationCallback playPromptResponseNotification = ((callEvent) -> { + PlayAudioResultEvent playAudioResultEvent = (PlayAudioResultEvent) callEvent; + Logger.logMessage(Logger.MessageType.INFORMATION, "Play audio status -- > " + playAudioResultEvent.getStatus()); + + if (playAudioResultEvent.getStatus().equals(CallingOperationStatus.COMPLETED)) { + EventDispatcher.getInstance().unsubscribe(CallingServerEventType.PLAY_AUDIO_RESULT_EVENT.toString(), + operationContext); + playAudioCompletedTask.complete(true); + } else if (playAudioResultEvent.getStatus().equals(CallingOperationStatus.FAILED)) { + playAudioCompletedTask.complete(false); + } + }); + + // Subscribe to event + EventDispatcher.getInstance().subscribe(CallingServerEventType.PLAY_AUDIO_RESULT_EVENT.toString(), + operationContext, playPromptResponseNotification); + } } From 66afc39dc807ee3f19a4c6d537e9bb1266932fa3 Mon Sep 17 00:00:00 2001 From: Fang Chen <16232002+fangchen0601@users.noreply.github.com> Date: Sun, 26 Dec 2021 15:34:01 -0800 Subject: [PATCH 15/30] refactor timeout logic --- .../utils/IncomingCallHandler.java | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java index 47b25d49..4953fb62 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java @@ -26,6 +26,7 @@ import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; public class IncomingCallHandler { private final CallingServerClient callingServerClient; @@ -159,22 +160,15 @@ private void playAudio(){ // listen to play audio events registerToPlayAudioResultEvent(response.getOperationContext()); - CompletableFuture maxWait = CompletableFuture.supplyAsync(() -> { - try { - TimeUnit.SECONDS.sleep(5); - } catch (InterruptedException ex) { - Logger.logMessage(Logger.MessageType.ERROR, " -- > " + ex.getMessage()); - } - return false; - }); - - CompletableFuture completedTask = CompletableFuture.anyOf(playAudioCompletedTask, maxWait); - if (completedTask.get() != playAudioCompletedTask.get()) { - Logger.logMessage(Logger.MessageType.INFORMATION, "No response from user in 30 sec, initiating hangup"); - playAudioCompletedTask.complete(false); - toneReceivedCompleteTask.complete(false); + try { + Logger.logMessage(Logger.MessageType.INFORMATION, "Waiting for playAudioCompletedTask or timeout"); + playAudioCompletedTask.get(30, TimeUnit.SECONDS); + Logger.logMessage(Logger.MessageType.INFORMATION, "Done playAudioCompletedTask "); + } catch (TimeoutException e) { + Logger.logMessage(Logger.MessageType.INFORMATION, "No response from user in 30 sec, initiating hangup"); + playAudioCompletedTask.complete(false); + //to do toneReceivedCompleteTask.complete(false); } - } } catch (CancellationException e) { Logger.logMessage(Logger.MessageType.INFORMATION, "Play audio operation cancelled"); @@ -182,7 +176,7 @@ private void playAudio(){ if (playAudioCompletedTask.isCancelled()) { Logger.logMessage(Logger.MessageType.INFORMATION, "Play audio operation cancelled"); } else { - Logger.logMessage(Logger.MessageType.INFORMATION, "Failure occured while playing audio on the call. Exception: " + ex.getMessage()); + Logger.logMessage(Logger.MessageType.INFORMATION, "Failure occurred while playing audio on the call. Exception: " + ex.getMessage()); } } } From 29c4c9d868dbfc1a89e748fc9caa5e8202de098b Mon Sep 17 00:00:00 2001 From: Fang Chen <16232002+fangchen0601@users.noreply.github.com> Date: Wed, 29 Dec 2021 10:51:24 -0800 Subject: [PATCH 16/30] refactor some code --- .../incomingcallsample/utils/IncomingCallHandler.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java index 4953fb62..b091cd3c 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java @@ -114,6 +114,7 @@ private String answerCall(String incomingCallContext) throws Exception{ private void registerToCallStateChangeEvent(String callConnectionId) { this.callConnectedTask = new CompletableFuture<>(); this.callTerminatedTask = new CompletableFuture<>(); + // Set the callback method NotificationCallback callStateChangeNotificaiton = (callEvent) -> { CallConnectionStateChangedEvent callStateChanged = (CallConnectionStateChangedEvent) callEvent; @@ -144,7 +145,7 @@ private void playAudio(){ try{ PlayAudioOptions playAudioOptions = new PlayAudioOptions(); - playAudioOptions.setLoop(true); + playAudioOptions.setLoop(false); playAudioOptions.setAudioFileId(UUID.randomUUID().toString()); playAudioOptions.setOperationContext(UUID.randomUUID().toString()); Logger.logMessage(Logger.MessageType.INFORMATION, "Performing PlayAudio operation"); @@ -165,9 +166,9 @@ private void playAudio(){ playAudioCompletedTask.get(30, TimeUnit.SECONDS); Logger.logMessage(Logger.MessageType.INFORMATION, "Done playAudioCompletedTask "); } catch (TimeoutException e) { - Logger.logMessage(Logger.MessageType.INFORMATION, "No response from user in 30 sec, initiating hangup"); - playAudioCompletedTask.complete(false); - //to do toneReceivedCompleteTask.complete(false); + Logger.logMessage(Logger.MessageType.INFORMATION, "No response from user in 30 sec, initiating hangup"); + playAudioCompletedTask.complete(false); + //to do toneReceivedCompleteTask.complete(false); } } } catch (CancellationException e) { @@ -182,7 +183,7 @@ private void playAudio(){ } private void registerToPlayAudioResultEvent(String operationContext) { - playAudioCompletedTask = new CompletableFuture<>(); + this.playAudioCompletedTask = new CompletableFuture<>(); NotificationCallback playPromptResponseNotification = ((callEvent) -> { PlayAudioResultEvent playAudioResultEvent = (PlayAudioResultEvent) callEvent; Logger.logMessage(Logger.MessageType.INFORMATION, "Play audio status -- > " + playAudioResultEvent.getStatus()); From 45dce7653f868ad3d078b1387482454acccf2dbd Mon Sep 17 00:00:00 2001 From: Fang Chen <16232002+fangchen0601@users.noreply.github.com> Date: Wed, 29 Dec 2021 15:41:21 -0800 Subject: [PATCH 17/30] refactor code, add hangup --- .../utils/IncomingCallHandler.java | 54 +++++++++++++++---- 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java index b091cd3c..8fce5742 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java @@ -45,6 +45,12 @@ public class IncomingCallHandler { public IncomingCallHandler(CallingServerClient callingServerClient, CallConfiguration callConfiguration) { this.callingServerClient = callingServerClient; this.callConfiguration = callConfiguration; + + this.callConnectedTask = new CompletableFuture<>(); + this.playAudioCompletedTask = new CompletableFuture<>(); + this.callTerminatedTask = new CompletableFuture<>(); + this.toneReceivedCompleteTask = new CompletableFuture<>(); + this.addParticipantCompleteTask = new CompletableFuture<>(); } public void report(String incomingCallContext){ @@ -63,6 +69,20 @@ public void report(String incomingCallContext){ playAudio(); Boolean playAudioCompleted = this.playAudioCompletedTask.get(); + /* + if (toneReceivedComplete) + { + string participant = targetParticipant; + Logger.LogMessage(Logger.MessageType.INFORMATION, $"Tranferring call to participant {participant}"); + var transferToParticipantCompleted = await TransferToParticipant(participant); + if (!transferToParticipantCompleted) + { + await RetryTransferToParticipantAsync(async () => await TransferToParticipant(participant)); + } + } + */ + hangup(); + // Wait for the call to terminate this.callTerminatedTask.get(); @@ -86,7 +106,6 @@ private String getResponse(Response response) } private String answerCall(String incomingCallContext) throws Exception{ - //answer the call AnswerCallOptions answerCallOptions = new AnswerCallOptions( new URI(this.callConfiguration.appCallbackUrl), new ArrayList() { @@ -112,9 +131,6 @@ private String answerCall(String incomingCallContext) throws Exception{ } private void registerToCallStateChangeEvent(String callConnectionId) { - this.callConnectedTask = new CompletableFuture<>(); - this.callTerminatedTask = new CompletableFuture<>(); - // Set the callback method NotificationCallback callStateChangeNotificaiton = (callEvent) -> { CallConnectionStateChangedEvent callStateChanged = (CallConnectionStateChangedEvent) callEvent; @@ -129,6 +145,10 @@ private void registerToCallStateChangeEvent(String callConnectionId) { EventDispatcher.getInstance() .unsubscribe(CallingServerEventType.CALL_CONNECTION_STATE_CHANGED_EVENT.toString(), callConnectionId); this.reportCancellationTokenSource.cancel(); + + this.callConnectedTask.complete(false); + this.toneReceivedCompleteTask.complete(false); + this.playAudioCompletedTask.complete(false); this.callTerminatedTask.complete(true); } }; @@ -145,7 +165,7 @@ private void playAudio(){ try{ PlayAudioOptions playAudioOptions = new PlayAudioOptions(); - playAudioOptions.setLoop(false); + playAudioOptions.setLoop(true); playAudioOptions.setAudioFileId(UUID.randomUUID().toString()); playAudioOptions.setOperationContext(UUID.randomUUID().toString()); Logger.logMessage(Logger.MessageType.INFORMATION, "Performing PlayAudio operation"); @@ -156,34 +176,35 @@ private void playAudio(){ response.getStatus().toString()); if(response.getStatus().equals(CallingOperationStatus.RUNNING)) { - Logger.logMessage(Logger.MessageType.INFORMATION, "Audio is playing, registering PlayAudioResultEvent"); - // listen to play audio events registerToPlayAudioResultEvent(response.getOperationContext()); try { - Logger.logMessage(Logger.MessageType.INFORMATION, "Waiting for playAudioCompletedTask or timeout"); + Logger.logMessage(Logger.MessageType.INFORMATION, "Audio is playing for 30 seconds, it can be interrupted by pressing 1"); playAudioCompletedTask.get(30, TimeUnit.SECONDS); - Logger.logMessage(Logger.MessageType.INFORMATION, "Done playAudioCompletedTask "); + Logger.logMessage(Logger.MessageType.INFORMATION, "Audio playing done."); } catch (TimeoutException e) { Logger.logMessage(Logger.MessageType.INFORMATION, "No response from user in 30 sec, initiating hangup"); - playAudioCompletedTask.complete(false); //to do toneReceivedCompleteTask.complete(false); + } finally { + playAudioCompletedTask.complete(true); } } } catch (CancellationException e) { Logger.logMessage(Logger.MessageType.INFORMATION, "Play audio operation cancelled"); + playAudioCompletedTask.complete(false); } catch (Exception ex) { if (playAudioCompletedTask.isCancelled()) { Logger.logMessage(Logger.MessageType.INFORMATION, "Play audio operation cancelled"); } else { Logger.logMessage(Logger.MessageType.INFORMATION, "Failure occurred while playing audio on the call. Exception: " + ex.getMessage()); } + playAudioCompletedTask.complete(false); } } private void registerToPlayAudioResultEvent(String operationContext) { - this.playAudioCompletedTask = new CompletableFuture<>(); + // Set the callback method NotificationCallback playPromptResponseNotification = ((callEvent) -> { PlayAudioResultEvent playAudioResultEvent = (PlayAudioResultEvent) callEvent; Logger.logMessage(Logger.MessageType.INFORMATION, "Play audio status -- > " + playAudioResultEvent.getStatus()); @@ -201,4 +222,15 @@ private void registerToPlayAudioResultEvent(String operationContext) { EventDispatcher.getInstance().subscribe(CallingServerEventType.PLAY_AUDIO_RESULT_EVENT.toString(), operationContext, playPromptResponseNotification); } + + private void hangup() { + if (reportCancellationToken.isCancellationRequested()) { + Logger.logMessage(Logger.MessageType.INFORMATION, "Cancellation request, Hangup will not be performed"); + return; + } + + Logger.logMessage(Logger.MessageType.INFORMATION, "Performing Hangup operation"); + Response response = this.callConnection.hangupWithResponse(null); + Logger.logMessage(Logger.MessageType.INFORMATION, "hangupWithResponse -- > " + getResponse(response)); + } } From e4c8ac9e841ce6049d95f64a8e01f17fbe66d35d Mon Sep 17 00:00:00 2001 From: Fang Chen <16232002+fangchen0601@users.noreply.github.com> Date: Thu, 30 Dec 2021 12:20:58 -0800 Subject: [PATCH 18/30] add dtmf envet handler --- .../EventHandler/EventDispatcher.java | 1 - .../utils/IncomingCallHandler.java | 90 +++++++++++++------ 2 files changed, 65 insertions(+), 26 deletions(-) diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/EventHandler/EventDispatcher.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/EventHandler/EventDispatcher.java index 9efa97ab..dc0d261f 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/EventHandler/EventDispatcher.java +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/EventHandler/EventDispatcher.java @@ -11,7 +11,6 @@ import com.azure.communication.callingserver.models.events.ToneReceivedEvent; import com.azure.core.models.CloudEvent; import com.azure.core.util.BinaryData; -import com.communication.incomingcallsample.Log.Logger; public class EventDispatcher { private static EventDispatcher instance = null; diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java index 8fce5742..c335d6ae 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java @@ -9,9 +9,12 @@ import com.azure.communication.callingserver.models.CallingOperationStatus; import com.azure.communication.callingserver.models.PlayAudioOptions; import com.azure.communication.callingserver.models.PlayAudioResult; +import com.azure.communication.callingserver.models.ToneInfo; +import com.azure.communication.callingserver.models.ToneValue; import com.azure.communication.callingserver.models.events.CallConnectionStateChangedEvent; import com.azure.communication.callingserver.models.events.CallingServerEventType; import com.azure.communication.callingserver.models.events.PlayAudioResultEvent; +import com.azure.communication.callingserver.models.events.ToneReceivedEvent; import com.azure.core.http.HttpHeader; import com.azure.core.http.rest.Response; import com.azure.cosmos.implementation.changefeed.CancellationToken; @@ -31,7 +34,7 @@ public class IncomingCallHandler { private final CallingServerClient callingServerClient; private final CallConfiguration callConfiguration; - private CallConnection callConnection = null; + private CallConnection callConnection; private CancellationTokenSource reportCancellationTokenSource; private CancellationToken reportCancellationToken; private String targetParticipant; @@ -45,6 +48,10 @@ public class IncomingCallHandler { public IncomingCallHandler(CallingServerClient callingServerClient, CallConfiguration callConfiguration) { this.callingServerClient = callingServerClient; this.callConfiguration = callConfiguration; + this.callConnection = null; + this.reportCancellationTokenSource = new CancellationTokenSource(); + this.reportCancellationToken = this.reportCancellationTokenSource.getToken(); + this.targetParticipant = this.callConfiguration.targetParticipant; this.callConnectedTask = new CompletableFuture<>(); this.playAudioCompletedTask = new CompletableFuture<>(); @@ -54,33 +61,20 @@ public IncomingCallHandler(CallingServerClient callingServerClient, CallConfigur } public void report(String incomingCallContext){ - this.reportCancellationTokenSource = new CancellationTokenSource(); - this.reportCancellationToken = this.reportCancellationTokenSource.getToken(); - try { - //answer the call + // answer the call String callConnectionId = answerCall(incomingCallContext); registerToCallStateChangeEvent(callConnectionId); + // wait for the call to get connected this.callConnectedTask.get(); - // to do: registerToDtmfResultEvent + registerToDtmfResultEvent(callConnection.getCallConnectionId()); - // to do: play audio + // play audio playAudio(); + // wait for audio play complete Boolean playAudioCompleted = this.playAudioCompletedTask.get(); - /* - if (toneReceivedComplete) - { - string participant = targetParticipant; - Logger.LogMessage(Logger.MessageType.INFORMATION, $"Tranferring call to participant {participant}"); - var transferToParticipantCompleted = await TransferToParticipant(participant); - if (!transferToParticipantCompleted) - { - await RetryTransferToParticipantAsync(async () => await TransferToParticipant(participant)); - } - } - */ hangup(); // Wait for the call to terminate @@ -181,25 +175,24 @@ private void playAudio(){ try { Logger.logMessage(Logger.MessageType.INFORMATION, "Audio is playing for 30 seconds, it can be interrupted by pressing 1"); - playAudioCompletedTask.get(30, TimeUnit.SECONDS); + this.playAudioCompletedTask.get(30, TimeUnit.SECONDS); Logger.logMessage(Logger.MessageType.INFORMATION, "Audio playing done."); } catch (TimeoutException e) { - Logger.logMessage(Logger.MessageType.INFORMATION, "No response from user in 30 sec, initiating hangup"); + Logger.logMessage(Logger.MessageType.INFORMATION, "No response from user in 30 sec."); //to do toneReceivedCompleteTask.complete(false); - } finally { - playAudioCompletedTask.complete(true); + this.playAudioCompletedTask.complete(false); } } } catch (CancellationException e) { Logger.logMessage(Logger.MessageType.INFORMATION, "Play audio operation cancelled"); - playAudioCompletedTask.complete(false); + this.playAudioCompletedTask.complete(false); } catch (Exception ex) { if (playAudioCompletedTask.isCancelled()) { Logger.logMessage(Logger.MessageType.INFORMATION, "Play audio operation cancelled"); } else { Logger.logMessage(Logger.MessageType.INFORMATION, "Failure occurred while playing audio on the call. Exception: " + ex.getMessage()); } - playAudioCompletedTask.complete(false); + this.playAudioCompletedTask.complete(false); } } @@ -233,4 +226,51 @@ private void hangup() { Response response = this.callConnection.hangupWithResponse(null); Logger.logMessage(Logger.MessageType.INFORMATION, "hangupWithResponse -- > " + getResponse(response)); } + + private void cancelMediaProcessing() { + if (reportCancellationToken.isCancellationRequested()) { + Logger.logMessage(Logger.MessageType.INFORMATION,"Cancellation request, CancelMediaProcessing will not be performed"); + return; + } + + Logger.logMessage(Logger.MessageType.INFORMATION, "Performing cancel media processing operation to stop playing audio"); + + Response cancelMediaResponse = this.callConnection.cancelAllMediaOperationsWithResponse(null); + + Logger.logMessage(Logger.MessageType.INFORMATION, "cancelAllMediaOperationsWithResponse -- > " + getResponse(cancelMediaResponse)); + } + + private void registerToDtmfResultEvent(String callLegId) { + toneReceivedCompleteTask = new CompletableFuture<>(); + + NotificationCallback dtmfReceivedEvent = ((callEvent) -> { + ToneReceivedEvent toneReceivedEvent = (ToneReceivedEvent) callEvent; + ToneInfo toneInfo = toneReceivedEvent.getToneInfo(); + + Logger.logMessage(Logger.MessageType.INFORMATION, "Tone received -- > : " + toneInfo.getTone()); + + if (toneInfo.getTone().equals(ToneValue.TONE1)) { + toneReceivedCompleteTask.complete(true); + + // transfer the call + Logger.logMessage(Logger.MessageType.INFORMATION, "Transferring call to participant" + this.targetParticipant); + + /* + Boolean addParticipantCompleted = addParticipant(participant); + if (!addParticipantCompleted) { + retryAddParticipantAsync(participant); + } + */ + + } else { + toneReceivedCompleteTask.complete(false); + } + EventDispatcher.getInstance().unsubscribe(CallingServerEventType.TONE_RECEIVED_EVENT.toString(), callLegId); + // cancel playing audio + cancelMediaProcessing(); + }); + // Subscribe to event + EventDispatcher.getInstance().subscribe(CallingServerEventType.TONE_RECEIVED_EVENT.toString(), callLegId, + dtmfReceivedEvent); + } } From 050374a04b9b7618eb6ddc3085196200aeaacb32 Mon Sep 17 00:00:00 2001 From: Fang Chen <16232002+fangchen0601@users.noreply.github.com> Date: Tue, 4 Jan 2022 16:56:20 -0800 Subject: [PATCH 19/30] add transfer feature --- .../Controller/IncomingCallController.java | 2 - .../utils/IncomingCallHandler.java | 98 +++++++++++++++---- 2 files changed, 80 insertions(+), 20 deletions(-) diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java index 65d58399..48ef147b 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java @@ -99,8 +99,6 @@ private ResponseEntity handleIncomingCall(String data) { try { String incomingCallContext = data.split("\"incomingCallContext\":\"")[1].split("\"}")[0]; - Logger.logMessage(Logger.MessageType.INFORMATION, "Microsoft.Communication.IncomingCall call context: " + incomingCallContext); - new IncomingCallHandler(this.callingServerClient, this.callConfiguration).report(incomingCallContext); } catch(Exception e) { String message = "Fails in OnIncomingCall ---> " + e.getMessage(); diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java index c335d6ae..44bcdb57 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java @@ -11,10 +11,14 @@ import com.azure.communication.callingserver.models.PlayAudioResult; import com.azure.communication.callingserver.models.ToneInfo; import com.azure.communication.callingserver.models.ToneValue; +import com.azure.communication.callingserver.models.TransferCallResult; import com.azure.communication.callingserver.models.events.CallConnectionStateChangedEvent; import com.azure.communication.callingserver.models.events.CallingServerEventType; import com.azure.communication.callingserver.models.events.PlayAudioResultEvent; import com.azure.communication.callingserver.models.events.ToneReceivedEvent; +import com.azure.communication.common.CommunicationIdentifier; +import com.azure.communication.common.CommunicationUserIdentifier; +import com.azure.communication.common.PhoneNumberIdentifier; import com.azure.core.http.HttpHeader; import com.azure.core.http.rest.Response; import com.azure.cosmos.implementation.changefeed.CancellationToken; @@ -30,8 +34,13 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.regex.Pattern; public class IncomingCallHandler { + private final String userIdentityRegex = "8:acs:[0-9a-fA-F]{8}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{12}_[0-9a-fA-F]{8}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{12}"; + private final String phoneIdentityRegex = "^\\+\\d{10,14}$"; + private final int MaxRetryAttemptCount = 3; + private final CallingServerClient callingServerClient; private final CallConfiguration callConfiguration; private CallConnection callConnection; @@ -43,7 +52,7 @@ public class IncomingCallHandler { private CompletableFuture playAudioCompletedTask; private CompletableFuture callTerminatedTask; private CompletableFuture toneReceivedCompleteTask; - private CompletableFuture addParticipantCompleteTask; + private CompletableFuture transferToParticipantCompleteTask; public IncomingCallHandler(CallingServerClient callingServerClient, CallConfiguration callConfiguration) { this.callingServerClient = callingServerClient; @@ -57,7 +66,7 @@ public IncomingCallHandler(CallingServerClient callingServerClient, CallConfigur this.playAudioCompletedTask = new CompletableFuture<>(); this.callTerminatedTask = new CompletableFuture<>(); this.toneReceivedCompleteTask = new CompletableFuture<>(); - this.addParticipantCompleteTask = new CompletableFuture<>(); + this.transferToParticipantCompleteTask = new CompletableFuture<>(); } public void report(String incomingCallContext){ @@ -68,13 +77,26 @@ public void report(String incomingCallContext){ // wait for the call to get connected this.callConnectedTask.get(); - registerToDtmfResultEvent(callConnection.getCallConnectionId()); + registerToDtmfResultEvent(callConnectionId); // play audio playAudio(); // wait for audio play complete Boolean playAudioCompleted = this.playAudioCompletedTask.get(); + // for debug purpose, will remove this line + this.toneReceivedCompleteTask.complete(true); + + Boolean toneReceivedCompleted = this.toneReceivedCompleteTask.get(); + if(toneReceivedCompleted) { + // transfer the call + Logger.logMessage(Logger.MessageType.INFORMATION, "Transferring call to participant" + this.targetParticipant); + Boolean transferToParticipantCompleted = transferToParticipant(this.targetParticipant); + if (!transferToParticipantCompleted) { + retryTransferToParticipant(this.targetParticipant); + } + } + hangup(); // Wait for the call to terminate @@ -143,6 +165,7 @@ private void registerToCallStateChangeEvent(String callConnectionId) { this.callConnectedTask.complete(false); this.toneReceivedCompleteTask.complete(false); this.playAudioCompletedTask.complete(false); + this.transferToParticipantCompleteTask.complete(false); this.callTerminatedTask.complete(true); } }; @@ -172,15 +195,19 @@ private void playAudio(){ if(response.getStatus().equals(CallingOperationStatus.RUNNING)) { // listen to play audio events registerToPlayAudioResultEvent(response.getOperationContext()); - try { Logger.logMessage(Logger.MessageType.INFORMATION, "Audio is playing for 30 seconds, it can be interrupted by pressing 1"); this.playAudioCompletedTask.get(30, TimeUnit.SECONDS); Logger.logMessage(Logger.MessageType.INFORMATION, "Audio playing done."); + //this.playAudioCompletedTask.complete(true); + //this.toneReceivedCompleteTask.complete(false); } catch (TimeoutException e) { Logger.logMessage(Logger.MessageType.INFORMATION, "No response from user in 30 sec."); - //to do toneReceivedCompleteTask.complete(false); - this.playAudioCompletedTask.complete(false); + //this.playAudioCompletedTask.complete(false); + //this.toneReceivedCompleteTask.complete(false); + } finally { + // cancel playing audio + cancelMediaProcessing(); } } } catch (CancellationException e) { @@ -246,25 +273,14 @@ private void registerToDtmfResultEvent(String callLegId) { NotificationCallback dtmfReceivedEvent = ((callEvent) -> { ToneReceivedEvent toneReceivedEvent = (ToneReceivedEvent) callEvent; ToneInfo toneInfo = toneReceivedEvent.getToneInfo(); - Logger.logMessage(Logger.MessageType.INFORMATION, "Tone received -- > : " + toneInfo.getTone()); if (toneInfo.getTone().equals(ToneValue.TONE1)) { toneReceivedCompleteTask.complete(true); - - // transfer the call - Logger.logMessage(Logger.MessageType.INFORMATION, "Transferring call to participant" + this.targetParticipant); - - /* - Boolean addParticipantCompleted = addParticipant(participant); - if (!addParticipantCompleted) { - retryAddParticipantAsync(participant); - } - */ - } else { toneReceivedCompleteTask.complete(false); } + EventDispatcher.getInstance().unsubscribe(CallingServerEventType.TONE_RECEIVED_EVENT.toString(), callLegId); // cancel playing audio cancelMediaProcessing(); @@ -273,4 +289,50 @@ private void registerToDtmfResultEvent(String callLegId) { EventDispatcher.getInstance().subscribe(CallingServerEventType.TONE_RECEIVED_EVENT.toString(), callLegId, dtmfReceivedEvent); } + + private boolean transferToParticipant(String targetParticipant) { + CommunicationIdentifier identifier = getCommunicationIdentifier(targetParticipant); + + if(identifier == null) { + Logger.logMessage(Logger.MessageType.INFORMATION, "Unknown identity provided. Enter valid phone number or communication user id"); + return true; + } + + String operationContext = UUID.randomUUID().toString(); + Response response = callConnection.transferToParticipantWithResponse(identifier, null, null, operationContext, null); + Logger.logMessage(Logger.MessageType.INFORMATION, "Transfer to participant response -- > " + getResponse(response)); + + Boolean transferToParticipantCompleted = false; + try { + transferToParticipantCompleted = this.transferToParticipantCompleteTask.get(); + } catch (Exception ex) { + Logger.logMessage(Logger.MessageType.ERROR, "Failed to add participant InterruptedException -- > " + ex.getMessage()); + } + return transferToParticipantCompleted; + } + + private void retryTransferToParticipant(String targetParticipant) { + int retryAttemptCount = 1; + while (retryAttemptCount <= this.MaxRetryAttemptCount) { + Logger.logMessage(Logger.MessageType.INFORMATION, "Retrying transfer participant attempt -- > " + retryAttemptCount + " is in progress"); + Boolean transferToParticipantCompleted = transferToParticipant(targetParticipant); + + if (transferToParticipantCompleted) { + return; + } else { + Logger.logMessage(Logger.MessageType.INFORMATION, "Retry transfer participant attempt -- > " + retryAttemptCount + " has failed"); + retryAttemptCount++; + } + } + } + + private CommunicationIdentifier getCommunicationIdentifier(String targetParticipant) { + if(Pattern.matches(userIdentityRegex, targetParticipant)) { + return new CommunicationUserIdentifier(targetParticipant); + } else if (Pattern.matches(phoneIdentityRegex, targetParticipant)) { + return new PhoneNumberIdentifier(targetParticipant); + } else { + return null; + } + } } From 9c98b89bb1e5830fcb689a0f01246a9afe4a134f Mon Sep 17 00:00:00 2001 From: Fang Chen <16232002+fangchen0601@users.noreply.github.com> Date: Mon, 10 Jan 2022 16:38:59 -0800 Subject: [PATCH 20/30] add transfer call feature and allow list --- .../Controller/IncomingCallController.java | 27 +++++++---- .../EventHandler/EventDispatcher.java | 6 +++ .../incomingcallsample/config.properties | 5 ++ .../utils/CallConfiguration.java | 5 +- .../utils/IncomingCallHandler.java | 46 +++++++++++-------- 5 files changed, 60 insertions(+), 29 deletions(-) diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java index 48ef147b..dae8d390 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java @@ -8,13 +8,10 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RequestBody; -import java.util.HashSet; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; -import java.util.Set; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import com.azure.communication.callingserver.CallingServerClient; import com.azure.communication.callingserver.CallingServerClientBuilder; @@ -50,6 +47,7 @@ public String callingServerAPICallBacks(@RequestBody(required = false) String da // Validating the incoming request by using secret set in config.properties if (this.eventAuthHandler.authorize(secretKey)) { + Logger.logMessage(Logger.MessageType.INFORMATION, "call back event: " + data); EventDispatcher.getInstance().processNotification(data); } else { Logger.logMessage(Logger.MessageType.ERROR, "Unauthorized Request"); @@ -80,7 +78,8 @@ public ResponseEntity onIncomingRequestAsync(@RequestBody(required = false) S if(type.equals("Microsoft.EventGrid.SubscriptionValidationEvent")) { return getRegisterEventGridResponse(eventGridEvent); } else if(type.equals("Microsoft.Communication.IncomingCall")) { - return handleIncomingCall(data); + String to = getRecipientMRI(eventGridEvent); + return handleIncomingCall(data, to); } else { return ResponseHandler.generateResponse("unknown EventGridEvent type: " + eventGridEvent.toString() , HttpStatus.BAD_REQUEST, null); } @@ -95,17 +94,25 @@ private static ResponseEntity getRegisterEventGridResponse(EventGridEvent eve "validationResponse", validationCode)); } - private ResponseEntity handleIncomingCall(String data) { + private ResponseEntity handleIncomingCall(String data, String to) { try { String incomingCallContext = data.split("\"incomingCallContext\":\"")[1].split("\"}")[0]; - - new IncomingCallHandler(this.callingServerClient, this.callConfiguration).report(incomingCallContext); + if(new ArrayList<>(Arrays.asList(this.callConfiguration.allowedRecipientList)).contains(to)){ + new IncomingCallHandler(this.callingServerClient, this.callConfiguration).report(incomingCallContext); + return ResponseHandler.generateResponse("answer call done", HttpStatus.OK, null); + } else { + Logger.logMessage(Logger.MessageType.INFORMATION, to + " is not in the recipient list that this app supports to answer, skip answering"); + return ResponseHandler.generateResponse("call to " + to + "ignored as it is not in the allow list", HttpStatus.OK, null); + } } catch(Exception e) { String message = "Fails in OnIncomingCall ---> " + e.getMessage(); Logger.logMessage(Logger.MessageType.ERROR, message); return ResponseHandler.generateResponse(message, HttpStatus.INTERNAL_SERVER_ERROR, null); } - return ResponseHandler.generateResponse("answer call done", HttpStatus.OK, null); + } + + private String getRecipientMRI(EventGridEvent eventGridEvent) { + return eventGridEvent.getSubject().split("recipient/")[1]; } } diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/EventHandler/EventDispatcher.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/EventHandler/EventDispatcher.java index dc0d261f..5b01f6bf 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/EventHandler/EventDispatcher.java +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/EventHandler/EventDispatcher.java @@ -9,6 +9,7 @@ import com.azure.communication.callingserver.models.events.CallingServerEventType; import com.azure.communication.callingserver.models.events.PlayAudioResultEvent; import com.azure.communication.callingserver.models.events.ToneReceivedEvent; +import com.azure.communication.callingserver.models.events.TransferCallResultEvent; import com.azure.core.models.CloudEvent; import com.azure.core.util.BinaryData; @@ -74,6 +75,8 @@ private CallingServerEventBase extractEvent(String content) { return PlayAudioResultEvent.deserialize(eventData); } else if (cloudEvent.getType().equals(CallingServerEventType.ADD_PARTICIPANT_RESULT_EVENT.toString())) { return AddParticipantResultEvent.deserialize(eventData); + } else if (cloudEvent.getType().equals(CallingServerEventType.TRANSFER_CALL_RESULT_EVENT.toString())) { + return TransferCallResultEvent.deserialize(eventData); } } catch (Exception ex) { System.out.println("Failed to parse request content Exception: " + ex.getMessage()); @@ -95,6 +98,9 @@ private String getEventKey(CallingServerEventBase callEventBase) { } else if (callEventBase.getClass() == AddParticipantResultEvent.class) { String operationContext = ((AddParticipantResultEvent) callEventBase).getOperationContext(); return buildEventKey(CallingServerEventType.ADD_PARTICIPANT_RESULT_EVENT.toString(), operationContext); + } else if (callEventBase.getClass() == TransferCallResultEvent.class) { + String operationContext = ((TransferCallResultEvent) callEventBase).getOperationContext(); + return buildEventKey(CallingServerEventType.TRANSFER_CALL_RESULT_EVENT.toString(), operationContext); } return null; diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/config.properties b/incomingcallsample/src/main/java/com/communication/incomingcallsample/config.properties index 4304cc4a..fca0829a 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/config.properties +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/config.properties @@ -16,6 +16,11 @@ AudiFileUri=%AudiFileUri% # or MRI(e.g. 8:acs:ab12b0ea-85ea-4f83-b0b6-84d90209c7c4_00000009-bce0-da09-54b7-xxxxxxxxxxxx) TargetParticipant=%TargetParticipant% +# List of MRIs to which that this applicaiton supports answering the call. +# separated by ; +# e.g. 8:acs:d0b8b7b5-66a2-4ede-b66c-c74c675e6ca2_0000000e-e5cf-c451-1252-573a0d001a7c;8:acs:d0b8b7b5-66a2-4ede-b66c-c74c675e6ca2_0000000e-e5cf-c451-1252-573a0d001a7d +AllowedRecipientList=%AllowedRecipientList% + # Query string for callback URL SecretValue=%SecretValue% diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/CallConfiguration.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/CallConfiguration.java index 87713376..a14e460f 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/CallConfiguration.java +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/CallConfiguration.java @@ -6,13 +6,15 @@ public class CallConfiguration { public String appCallbackUrl; public String audioFileUrl; public String targetParticipant; + public String[] allowedRecipientList; - public CallConfiguration(String connectionString, String appBaseUrl, String audioFileUrl, String targetParticipant, String queryString) { + public CallConfiguration(String connectionString, String appBaseUrl, String audioFileUrl, String targetParticipant, String[] allowedRecipientList, String queryString) { this.connectionString = connectionString; this.appBaseUrl = appBaseUrl; this.appCallbackUrl = this.appBaseUrl + "/CallingServerAPICallBacks?" + queryString; this.audioFileUrl = audioFileUrl; this.targetParticipant = targetParticipant; + this.allowedRecipientList = allowedRecipientList; } public static CallConfiguration GetCallConfiguration(ConfigurationManager configurationManager, String queryString) { @@ -21,6 +23,7 @@ public static CallConfiguration GetCallConfiguration(ConfigurationManager config configurationManager.getAppSettings("AppCallBackUri"), configurationManager.getAppSettings("AudiFileUri"), configurationManager.getAppSettings("TargetParticipant"), + configurationManager.getAppSettings("AllowedRecipientList").split(";"), queryString); } } diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java index 44bcdb57..f223b593 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java @@ -16,6 +16,7 @@ import com.azure.communication.callingserver.models.events.CallingServerEventType; import com.azure.communication.callingserver.models.events.PlayAudioResultEvent; import com.azure.communication.callingserver.models.events.ToneReceivedEvent; +import com.azure.communication.callingserver.models.events.TransferCallResultEvent; import com.azure.communication.common.CommunicationIdentifier; import com.azure.communication.common.CommunicationUserIdentifier; import com.azure.communication.common.PhoneNumberIdentifier; @@ -97,7 +98,7 @@ public void report(String incomingCallContext){ } } - hangup(); + // hangup(); // Wait for the call to terminate this.callTerminatedTask.get(); @@ -161,11 +162,6 @@ private void registerToCallStateChangeEvent(String callConnectionId) { EventDispatcher.getInstance() .unsubscribe(CallingServerEventType.CALL_CONNECTION_STATE_CHANGED_EVENT.toString(), callConnectionId); this.reportCancellationTokenSource.cancel(); - - this.callConnectedTask.complete(false); - this.toneReceivedCompleteTask.complete(false); - this.playAudioCompletedTask.complete(false); - this.transferToParticipantCompleteTask.complete(false); this.callTerminatedTask.complete(true); } }; @@ -196,15 +192,11 @@ private void playAudio(){ // listen to play audio events registerToPlayAudioResultEvent(response.getOperationContext()); try { - Logger.logMessage(Logger.MessageType.INFORMATION, "Audio is playing for 30 seconds, it can be interrupted by pressing 1"); - this.playAudioCompletedTask.get(30, TimeUnit.SECONDS); - Logger.logMessage(Logger.MessageType.INFORMATION, "Audio playing done."); - //this.playAudioCompletedTask.complete(true); - //this.toneReceivedCompleteTask.complete(false); + Logger.logMessage(Logger.MessageType.INFORMATION, "Audio is playing for 30 seconds, it can be interrupted by pressing 1"); + this.playAudioCompletedTask.get(30, TimeUnit.SECONDS); + Logger.logMessage(Logger.MessageType.INFORMATION, "Audio playing done."); } catch (TimeoutException e) { Logger.logMessage(Logger.MessageType.INFORMATION, "No response from user in 30 sec."); - //this.playAudioCompletedTask.complete(false); - //this.toneReceivedCompleteTask.complete(false); } finally { // cancel playing audio cancelMediaProcessing(); @@ -268,17 +260,15 @@ private void cancelMediaProcessing() { } private void registerToDtmfResultEvent(String callLegId) { - toneReceivedCompleteTask = new CompletableFuture<>(); - NotificationCallback dtmfReceivedEvent = ((callEvent) -> { ToneReceivedEvent toneReceivedEvent = (ToneReceivedEvent) callEvent; ToneInfo toneInfo = toneReceivedEvent.getToneInfo(); Logger.logMessage(Logger.MessageType.INFORMATION, "Tone received -- > : " + toneInfo.getTone()); if (toneInfo.getTone().equals(ToneValue.TONE1)) { - toneReceivedCompleteTask.complete(true); + this.toneReceivedCompleteTask.complete(true); } else { - toneReceivedCompleteTask.complete(false); + this.toneReceivedCompleteTask.complete(false); } EventDispatcher.getInstance().unsubscribe(CallingServerEventType.TONE_RECEIVED_EVENT.toString(), callLegId); @@ -299,7 +289,8 @@ private boolean transferToParticipant(String targetParticipant) { } String operationContext = UUID.randomUUID().toString(); - Response response = callConnection.transferToParticipantWithResponse(identifier, null, null, operationContext, null); + RegisterToTransferParticipantsResultEvent(operationContext); + Response response = this.callConnection.transferToParticipantWithResponse(identifier, null, null, operationContext, null); Logger.logMessage(Logger.MessageType.INFORMATION, "Transfer to participant response -- > " + getResponse(response)); Boolean transferToParticipantCompleted = false; @@ -311,6 +302,25 @@ private boolean transferToParticipant(String targetParticipant) { return transferToParticipantCompleted; } + private void RegisterToTransferParticipantsResultEvent(String operationContext) { + NotificationCallback transferParticipantsResultEvent = ((callEvent) -> { + TransferCallResultEvent transferCallResultEvent = (TransferCallResultEvent) callEvent; + CallingOperationStatus operationStatus = transferCallResultEvent.getStatus(); + Logger.logMessage(Logger.MessageType.INFORMATION, "transfer to participant status -- > " + operationStatus); + if (operationStatus.equals(CallingOperationStatus.COMPLETED)) { + this.transferToParticipantCompleteTask.complete(true); + } else if (operationStatus.equals(CallingOperationStatus.FAILED)) { + this.transferToParticipantCompleteTask.complete(false); + } + EventDispatcher.getInstance().unsubscribe(CallingServerEventType.TRANSFER_CALL_RESULT_EVENT.toString(), + operationContext); + }); + + // Subscribe to event + EventDispatcher.getInstance().subscribe(CallingServerEventType.TRANSFER_CALL_RESULT_EVENT.toString(), + operationContext, transferParticipantsResultEvent); + } + private void retryTransferToParticipant(String targetParticipant) { int retryAttemptCount = 1; while (retryAttemptCount <= this.MaxRetryAttemptCount) { From 863eb076c3c2cb76768c3c3b39756e076270b45f Mon Sep 17 00:00:00 2001 From: Fang Chen <16232002+fangchen0601@users.noreply.github.com> Date: Mon, 10 Jan 2022 17:34:00 -0800 Subject: [PATCH 21/30] put transfer call into dtmf event handler --- .../utils/IncomingCallHandler.java | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java index f223b593..b1acb45f 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java @@ -83,23 +83,14 @@ public void report(String incomingCallContext){ // play audio playAudio(); // wait for audio play complete - Boolean playAudioCompleted = this.playAudioCompletedTask.get(); + this.playAudioCompletedTask.get(); - // for debug purpose, will remove this line - this.toneReceivedCompleteTask.complete(true); - - Boolean toneReceivedCompleted = this.toneReceivedCompleteTask.get(); - if(toneReceivedCompleted) { - // transfer the call - Logger.logMessage(Logger.MessageType.INFORMATION, "Transferring call to participant" + this.targetParticipant); - Boolean transferToParticipantCompleted = transferToParticipant(this.targetParticipant); - if (!transferToParticipantCompleted) { - retryTransferToParticipant(this.targetParticipant); - } + // No ToneReceived event fired, and since audio playing is done, drop the call + if(!this.toneReceivedCompleteTask.isDone()){ + Logger.logMessage(Logger.MessageType.INFORMATION, "dtmf tone not sent from caller, dropped the call"); + hangup(); } - // hangup(); - // Wait for the call to terminate this.callTerminatedTask.get(); @@ -192,7 +183,7 @@ private void playAudio(){ // listen to play audio events registerToPlayAudioResultEvent(response.getOperationContext()); try { - Logger.logMessage(Logger.MessageType.INFORMATION, "Audio is playing for 30 seconds, it can be interrupted by pressing 1"); + Logger.logMessage(Logger.MessageType.INFORMATION, "Audio is playing for 30 seconds, it can be interrupted by pressing 1 to transfer the call"); this.playAudioCompletedTask.get(30, TimeUnit.SECONDS); Logger.logMessage(Logger.MessageType.INFORMATION, "Audio playing done."); } catch (TimeoutException e) { @@ -224,9 +215,9 @@ private void registerToPlayAudioResultEvent(String operationContext) { if (playAudioResultEvent.getStatus().equals(CallingOperationStatus.COMPLETED)) { EventDispatcher.getInstance().unsubscribe(CallingServerEventType.PLAY_AUDIO_RESULT_EVENT.toString(), operationContext); - playAudioCompletedTask.complete(true); + this.playAudioCompletedTask.complete(true); } else if (playAudioResultEvent.getStatus().equals(CallingOperationStatus.FAILED)) { - playAudioCompletedTask.complete(false); + this.playAudioCompletedTask.complete(false); } }); @@ -266,6 +257,11 @@ private void registerToDtmfResultEvent(String callLegId) { Logger.logMessage(Logger.MessageType.INFORMATION, "Tone received -- > : " + toneInfo.getTone()); if (toneInfo.getTone().equals(ToneValue.TONE1)) { + Logger.logMessage(Logger.MessageType.INFORMATION, "Transferring call to participant" + this.targetParticipant); + Boolean transferToParticipantCompleted = transferToParticipant(this.targetParticipant); + if (!transferToParticipantCompleted) { + retryTransferToParticipant(this.targetParticipant); + } this.toneReceivedCompleteTask.complete(true); } else { this.toneReceivedCompleteTask.complete(false); From d1a557524490c862a4591c7defe88c6ea9425b90 Mon Sep 17 00:00:00 2001 From: Fang Chen <16232002+fangchen0601@users.noreply.github.com> Date: Mon, 10 Jan 2022 17:34:54 -0800 Subject: [PATCH 22/30] remove unused project --- IncomingCallQuickstart/pom.xml | 157 ------------------ .../incomingcallquickstart/App.java | 24 --- .../ConfigurationManager.java | 43 ----- .../Controllers/InComingCallController.java | 26 --- .../incomingcallquickstart/config.properties | 30 ---- 5 files changed, 280 deletions(-) delete mode 100644 IncomingCallQuickstart/pom.xml delete mode 100644 IncomingCallQuickstart/src/main/java/com/communication/incomingcallquickstart/App.java delete mode 100644 IncomingCallQuickstart/src/main/java/com/communication/incomingcallquickstart/ConfigurationManager.java delete mode 100644 IncomingCallQuickstart/src/main/java/com/communication/incomingcallquickstart/Controllers/InComingCallController.java delete mode 100644 IncomingCallQuickstart/src/main/java/com/communication/incomingcallquickstart/config.properties diff --git a/IncomingCallQuickstart/pom.xml b/IncomingCallQuickstart/pom.xml deleted file mode 100644 index 7d5fac97..00000000 --- a/IncomingCallQuickstart/pom.xml +++ /dev/null @@ -1,157 +0,0 @@ - - - - 4.0.0 - - - org.springframework.boot - spring-boot-starter-parent - 2.4.4 - - - - com.communication.incomingcallquickstart - communication-incomingcallquickstart - 1.0-SNAPSHOT - - communication-incomingcallquickstart - - http://www.example.com - - - UTF-8 - 1.7 - 1.7 - - - - - junit - junit - 4.11 - test - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-test - test - - - org.apache.httpcomponents - httpclient - 4.5.13 - - - com.azure - azure-core - 1.19.0 - - - com.azure - azure-identity - 1.3.5 - - - com.azure - azure-communication-identity - 1.1.4 - - - com.azure - azure-communication-common - - - - - com.azure - azure-communication-callingserver - 1.0.0-beta.4 - - - com.microsoft.cognitiveservices.speech - client-sdk - 1.12.1 - - - com.azure - azure-messaging-eventgrid - 4.6.0 - - - com.azure - azure-cosmos - 4.18.0 - - - - - - maven-cognitiveservices-speech - Microsoft Cognitive Services Speech Maven Repository - https://csspeechstorage.blob.core.windows.net/maven/ - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - maven-clean-plugin - 3.1.0 - - - maven-resources-plugin - 3.0.2 - - - maven-compiler-plugin - 3.8.0 - - - maven-surefire-plugin - 2.22.1 - - - maven-jar-plugin - 3.0.2 - - - maven-deploy-plugin - 2.8.2 - - - maven-site-plugin - 3.7.1 - - - maven-project-info-reports-plugin - 3.0.0 - - - org.codehaus.mojo - exec-maven-plugin - 3.0.0 - - - - java - - - - - com.communication.incomingcallquickstart.App - - - - - - diff --git a/IncomingCallQuickstart/src/main/java/com/communication/incomingcallquickstart/App.java b/IncomingCallQuickstart/src/main/java/com/communication/incomingcallquickstart/App.java deleted file mode 100644 index 422a0ea1..00000000 --- a/IncomingCallQuickstart/src/main/java/com/communication/incomingcallquickstart/App.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.communication.incomingcallquickstart; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import java.util.*; - -@SpringBootApplication -public class App -{ - final static String url = "http://localhost:9007"; - final static String serverPort = "9007"; - public static void main( String[] args ) - { - SpringApplication application = new SpringApplication(App.class); - application.setDefaultProperties(Collections.singletonMap("server.port", serverPort)); - application.run(args); - - // Logger.logMessage(Logger.MessageType.INFORMATION, "Starting ACS InComing Call Sample App "); - - // Get configuration properties - ConfigurationManager configurationManager = ConfigurationManager.getInstance(); - configurationManager.loadAppSettings(); - } -} diff --git a/IncomingCallQuickstart/src/main/java/com/communication/incomingcallquickstart/ConfigurationManager.java b/IncomingCallQuickstart/src/main/java/com/communication/incomingcallquickstart/ConfigurationManager.java deleted file mode 100644 index 87841b16..00000000 --- a/IncomingCallQuickstart/src/main/java/com/communication/incomingcallquickstart/ConfigurationManager.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.communication.incomingcallquickstart; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; -import java.util.Properties; - -public class ConfigurationManager { - private static ConfigurationManager configurationManager = null; - private final Properties appSettings = new Properties(); - - private ConfigurationManager() { - } - - // static method to create instance of ConfigurationManager class - public static ConfigurationManager getInstance() { - if (configurationManager == null) { - configurationManager = new ConfigurationManager(); - } - return configurationManager; - } - - public void loadAppSettings() { - try { - File configFile = new File("src/main/java/com/communication/incomingcallquickstart/config.properties"); - FileReader reader = new FileReader(configFile); - appSettings.load(reader); - reader.close(); - } catch (FileNotFoundException ex) { - // Logger.logMessage(Logger.MessageType.INFORMATION,"Loading app settings failed with error -- > " + ex.getMessage()); - } catch (IOException ex) { - // Logger.logMessage(Logger.MessageType.ERROR,"Loading app settings failed with error -- > " + ex.getMessage()); - } - } - - public String getAppSettings(String key) { - if (!key.isEmpty()) { - return appSettings.getProperty(key); - } - return ""; - } -} \ No newline at end of file diff --git a/IncomingCallQuickstart/src/main/java/com/communication/incomingcallquickstart/Controllers/InComingCallController.java b/IncomingCallQuickstart/src/main/java/com/communication/incomingcallquickstart/Controllers/InComingCallController.java deleted file mode 100644 index 2c64cc58..00000000 --- a/IncomingCallQuickstart/src/main/java/com/communication/incomingcallquickstart/Controllers/InComingCallController.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.communication.incomingcallquickstart.Controllers; - -import java.net.URI; - -import org.springframework.core.io.InputStreamResource; -import org.springframework.http.ResponseEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import java.io.File; -import java.io.FileInputStream; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.bind.annotation.GetMapping; - -@RestController -public class InComingCallController { - - @GetMapping("/api/incomingcall") - public static String getCheck() - { - return "OK"; - } -} diff --git a/IncomingCallQuickstart/src/main/java/com/communication/incomingcallquickstart/config.properties b/IncomingCallQuickstart/src/main/java/com/communication/incomingcallquickstart/config.properties deleted file mode 100644 index fe01bf54..00000000 --- a/IncomingCallQuickstart/src/main/java/com/communication/incomingcallquickstart/config.properties +++ /dev/null @@ -1,30 +0,0 @@ -# app settings - -#1. Configurations related to Communication Service resource - -# Connection string of Azure Communication Service Resource. -Connectionstring=endpoint=https://fancheacsdev.communication.azure.com/;accesskey=CO4Crd2u1EeMVqvye3HaI4dBBZSIuQZVlMpzVS9UVh4k5OGBzBOhiT3XjfI7itaB5t82M+gtb9W3G8rmgNb4HQ== - -# Phone number provisioned for the ACS resource (in E.164 Format, e.g. +1425XXXYYYY). This is an alternative phone id -SourcePhone=+18332142966 - -# Destination identities to call. -# Format: "Outbound Target1(PhoneNumber), Transfer Target1(PhoneNumber/MRI);Outbound Target2(PhoneNumber), Transfer Target2(PhoneNumber/MRI);Outbound Target3(PhoneNumber), Transfer Target3(PhoneNumber/MRI)" -# For e.g. "+1425XXXAAAA,8:acs:ab12b0ea-85ea-4f83-b0b6-84d90209c7c4_00000009-bce0-da09-54b7-xxxxxxxxxxxx;+1425XXXBBBB,+1425XXXCCCC" -DestinationIdentities=+16314558075,+17788798076 - -MaxRetryCount=2 - -# 2. Configurations related to environment - -# Directory where ngrok.exe is saved. -NgrokExePath="C:\\ProgramData\\chocolatey\\lib\\ngrok\\tools\\" -# Secret for validating incoming request. -SecretPlaceholder=h3llowW0rld - -# Cognitive service key (Optional). -CognitiveServiceKey= -# Cognitive service region (Optional). -CognitiveServiceRegion= -# Custom message that will be translated by Azure Cognitive service (Optional). -CustomMessage=Hello, this is a reminder call. If you would like to speak with a representative Press 1 or 2 if you want to hang up. From e8d75f73bdfa47cb4f82b28a6b1fc8c40567a672 Mon Sep 17 00:00:00 2001 From: Fang Chen <16232002+fangchen0601@users.noreply.github.com> Date: Wed, 12 Jan 2022 18:37:32 -0800 Subject: [PATCH 23/30] use internal feed for sdk --- incomingcallsample/pom.xml | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/incomingcallsample/pom.xml b/incomingcallsample/pom.xml index 4da1755f..731bcc42 100644 --- a/incomingcallsample/pom.xml +++ b/incomingcallsample/pom.xml @@ -3,6 +3,32 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 + + + azure-sdk-for-java + https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-java/maven/v1 + + true + + + true + + + + + + + azure-sdk-for-java + https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-java/maven/v1 + + true + + + true + + + + org.springframework.boot spring-boot-starter-parent @@ -60,8 +86,8 @@ com.azure azure-communication-callingserver - 1.0.0-beta.4 - + 1.0.0-alpha.20220112.2 + From ca8c4a180c402f5a6a502fe296ddbcc8e311b9e9 Mon Sep 17 00:00:00 2001 From: Fang Chen <16232002+fangchen0601@users.noreply.github.com> Date: Fri, 14 Jan 2022 11:37:58 -0800 Subject: [PATCH 24/30] update code for testing dtmf --- .../incomingcallsample/utils/IncomingCallHandler.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java index b1acb45f..7d8244c9 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java @@ -188,8 +188,6 @@ private void playAudio(){ Logger.logMessage(Logger.MessageType.INFORMATION, "Audio playing done."); } catch (TimeoutException e) { Logger.logMessage(Logger.MessageType.INFORMATION, "No response from user in 30 sec."); - } finally { - // cancel playing audio cancelMediaProcessing(); } } @@ -263,13 +261,14 @@ private void registerToDtmfResultEvent(String callLegId) { retryTransferToParticipant(this.targetParticipant); } this.toneReceivedCompleteTask.complete(true); + + this.playAudioCompletedTask.complete(true); + } else { this.toneReceivedCompleteTask.complete(false); } EventDispatcher.getInstance().unsubscribe(CallingServerEventType.TONE_RECEIVED_EVENT.toString(), callLegId); - // cancel playing audio - cancelMediaProcessing(); }); // Subscribe to event EventDispatcher.getInstance().subscribe(CallingServerEventType.TONE_RECEIVED_EVENT.toString(), callLegId, From 02cacf71df1bd85a69f9a2f67aa2f6ca8f558a05 Mon Sep 17 00:00:00 2001 From: Fang Chen <16232002+fangchen0601@users.noreply.github.com> Date: Fri, 14 Jan 2022 12:03:07 -0800 Subject: [PATCH 25/30] update readme --- incomingcallsample/readme.md | 38 ++++++++++++------- .../Controller/IncomingCallController.java | 1 - 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/incomingcallsample/readme.md b/incomingcallsample/readme.md index 9f5c4f8d..1027f0cb 100644 --- a/incomingcallsample/readme.md +++ b/incomingcallsample/readme.md @@ -1,25 +1,37 @@ -## 1.Start web app locally -```dotnetcli -mvn clean package -java -jar .\target\incomingcallsample-0.0.1-SNAPSHOT.jar +How to run +1. Create ACS resource from Azure Portal + +2. Create 3 ACS User Identities(MRI) +Example ``` +User 1: 8:acs:- +User 2: 8:acs:- +User 3: 8:acs:- +``` + +3. Put User2 MRI into the config AllowedRecipientList + +4. Put User3 MRI into the config TargetParticipant -## 2.Start ngrok +5. Start ngrok ```dotnetcli ngrok http 9008 ``` -## 3.Try to access from brower -### https://ngrok_url/hello +6. Update config with ngrok endpoint. Example +``` +AppCallBackUri=https://4087-75-155-234-140.ngrok.io +``` -example: -https://19ad-75-155-234-140.ngrok.io/hello +7. Start sample app locally +```dotnetcli +mvn clean package +java -jar .\target\incomingcallsample-0.0.1-SNAPSHOT.jar +``` -## 4. Register webhook to your ACS resource +8. Register webhook to your ACS resource ```dotnetcli armclient put "/subscriptions//resourceGroups//providers/Microsoft.Communication/CommunicationServices//providers/Microsoft.EventGrid/eventSubscriptions/IncomingCallEventSub?api-version=2020-06-01" "{'properties':{'destination':{'properties':{'endpointUrl':'https:///OnIncomingCall'},'endpointType':'WebHook'},'filter':{'includedEventTypes': ['Microsoft.Communication.IncomingCall']}}}" -verbose ``` -## Reference -1. https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/eventgrid/azure-messaging-eventgrid/src/samples/java/com/azure/messaging/eventgrid/samples/DeserializeEventsFromString.java -2. https://www.jamessturtevant.com/posts/Validating-Azure-Event-Grid-WebHook-in-Nodejs/ \ No newline at end of file +9. Make a call from User1 to User2 \ No newline at end of file diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java index dae8d390..6dc1b6c0 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java @@ -2,7 +2,6 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; From 3b3da7a99c5ac80ce081c10b97499ddc98cccd0b Mon Sep 17 00:00:00 2001 From: Fang Chen <16232002+fangchen0601@users.noreply.github.com> Date: Mon, 17 Jan 2022 13:53:33 -0800 Subject: [PATCH 26/30] reorder import --- .../Controller/IncomingCallController.java | 11 +++++------ .../IncomingcallsampleApplication.java | 4 ++-- .../utils/IncomingCallHandler.java | 18 +++++++++--------- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java index 6dc1b6c0..418af1d7 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java @@ -1,5 +1,10 @@ package com.communication.incomingcallsample.Controller; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; @@ -7,11 +12,6 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RequestBody; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; - import com.azure.communication.callingserver.CallingServerClient; import com.azure.communication.callingserver.CallingServerClientBuilder; import com.azure.messaging.eventgrid.EventGridEvent; @@ -57,7 +57,6 @@ public String callingServerAPICallBacks(@RequestBody(required = false) String da @PostMapping(value = "/OnIncomingCall") public ResponseEntity onIncomingRequestAsync(@RequestBody(required = false) String data) { - // parse EventGridEvent EventGridEvent eventGridEvent = null; try{ diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/IncomingcallsampleApplication.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/IncomingcallsampleApplication.java index c144287b..2dde202b 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/IncomingcallsampleApplication.java +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/IncomingcallsampleApplication.java @@ -1,13 +1,13 @@ package com.communication.incomingcallsample; +import java.util.*; + import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ComponentScan; -import java.util.*; import com.communication.incomingcallsample.Controller.IncomingCallController; - @SpringBootApplication @ComponentScan(basePackageClasses= IncomingCallController.class) public class IncomingcallsampleApplication { diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java index 7d8244c9..6a4d4f8f 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java @@ -1,5 +1,14 @@ package com.communication.incomingcallsample.Utils; +import java.net.URI; +import java.util.ArrayList; +import java.util.UUID; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.regex.Pattern; + import com.azure.communication.callingserver.CallConnection; import com.azure.communication.callingserver.CallingServerClient; import com.azure.communication.callingserver.models.AnswerCallOptions; @@ -28,15 +37,6 @@ import com.communication.incomingcallsample.EventHandler.NotificationCallback; import com.communication.incomingcallsample.Log.Logger; -import java.net.URI; -import java.util.ArrayList; -import java.util.UUID; -import java.util.concurrent.CancellationException; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.regex.Pattern; - public class IncomingCallHandler { private final String userIdentityRegex = "8:acs:[0-9a-fA-F]{8}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{12}_[0-9a-fA-F]{8}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{12}"; private final String phoneIdentityRegex = "^\\+\\d{10,14}$"; From 35144532a29c44315615189ed8a93d9af8514fe8 Mon Sep 17 00:00:00 2001 From: Fang Chen <16232002+fangchen0601@users.noreply.github.com> Date: Tue, 18 Jan 2022 11:47:24 -0800 Subject: [PATCH 27/30] fix compile error --- .../Controller/IncomingCallController.java | 8 ++++---- .../incomingcallsample/utils/CallConfiguration.java | 2 +- .../incomingcallsample/utils/ConfigurationManager.java | 2 +- .../incomingcallsample/utils/IncomingCallHandler.java | 2 +- .../incomingcallsample/utils/ResponseHandler.java | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java index 418af1d7..57a956d4 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java @@ -19,10 +19,10 @@ import com.communication.incomingcallsample.EventHandler.EventAuthHandler; import com.communication.incomingcallsample.EventHandler.EventDispatcher; import com.communication.incomingcallsample.Log.Logger; -import com.communication.incomingcallsample.Utils.CallConfiguration; -import com.communication.incomingcallsample.Utils.ConfigurationManager; -import com.communication.incomingcallsample.Utils.IncomingCallHandler; -import com.communication.incomingcallsample.Utils.ResponseHandler; +import com.communication.incomingcallsample.utils.CallConfiguration; +import com.communication.incomingcallsample.utils.ConfigurationManager; +import com.communication.incomingcallsample.utils.IncomingCallHandler; +import com.communication.incomingcallsample.utils.ResponseHandler; @RestController public class IncomingCallController { diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/CallConfiguration.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/CallConfiguration.java index a14e460f..3aefb51d 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/CallConfiguration.java +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/CallConfiguration.java @@ -1,4 +1,4 @@ -package com.communication.incomingcallsample.Utils; +package com.communication.incomingcallsample.utils; public class CallConfiguration { public String connectionString; diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/ConfigurationManager.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/ConfigurationManager.java index 6e8f1776..b2182a94 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/ConfigurationManager.java +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/ConfigurationManager.java @@ -1,4 +1,4 @@ -package com.communication.incomingcallsample.Utils; +package com.communication.incomingcallsample.utils; import java.io.File; import java.io.FileNotFoundException; diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java index 6a4d4f8f..16afa8c9 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java @@ -1,4 +1,4 @@ -package com.communication.incomingcallsample.Utils; +package com.communication.incomingcallsample.utils; import java.net.URI; import java.util.ArrayList; diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/ResponseHandler.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/ResponseHandler.java index 29d7ef0e..23428863 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/ResponseHandler.java +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/ResponseHandler.java @@ -1,4 +1,4 @@ -package com.communication.incomingcallsample.Utils; +package com.communication.incomingcallsample.utils; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; From 3602a3e5f7b68c7c0d57d6c23509e7c6e51314f7 Mon Sep 17 00:00:00 2001 From: Fang Chen <16232002+fangchen0601@users.noreply.github.com> Date: Tue, 18 Jan 2022 12:11:11 -0800 Subject: [PATCH 28/30] add logger --- .../incomingcallsample/logger/Logger.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 incomingcallsample/src/main/java/com/communication/incomingcallsample/logger/Logger.java diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/logger/Logger.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/logger/Logger.java new file mode 100644 index 00000000..1e908476 --- /dev/null +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/logger/Logger.java @@ -0,0 +1,39 @@ +package com.communication.incomingcallsample.log; + +import com.azure.messaging.eventgrid.EventGridEvent; + +public class Logger { + //Caution: Logging should be removed/disabled if you want to use this sample in production to avoid exposing sensitive information + public enum MessageType + { + INFORMATION, + ERROR + } + + /// + /// Log message to console + /// + /// Type of the message: Information or Error + /// Message string + public static void logMessage(MessageType messageType, String message) + { + String logMessage; + logMessage = messageType + " " + message; + System.out.println(logMessage); + } + + public static void logEventGridEvent(MessageType messageType, EventGridEvent eventGridEvent){ + String log = new StringBuilder() + .append(messageType + " ") + .append("OnIncomingCall API POST request EventGridEvent---->") + .append(" type: " + eventGridEvent.getEventType()) + .append(";") + .append(" topic: " + eventGridEvent.getTopic()) + .append(";") + .append(" subject: " + eventGridEvent.getSubject()) + .append(";") + .append(" data: " + eventGridEvent.getData()) + .toString(); + System.out.println(log); + } +} \ No newline at end of file From 801ac75fa57fd092ce8bb879b2e96cb78ca56f34 Mon Sep 17 00:00:00 2001 From: Fang Chen <16232002+fangchen0601@users.noreply.github.com> Date: Tue, 18 Jan 2022 13:11:06 -0800 Subject: [PATCH 29/30] update import --- .../incomingcallsample/utils/ConfigurationManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/ConfigurationManager.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/ConfigurationManager.java index b2182a94..ab83a6b0 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/ConfigurationManager.java +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/ConfigurationManager.java @@ -6,7 +6,7 @@ import java.io.IOException; import java.util.Properties; -import com.communication.incomingcallsample.Log.Logger; +import com.communication.incomingcallsample.logger.Logger; public class ConfigurationManager { private static ConfigurationManager configurationManager = null; From 712e5e50d06d3e3367709200b4cb1a029c20547e Mon Sep 17 00:00:00 2001 From: Fang Chen <16232002+fangchen0601@users.noreply.github.com> Date: Tue, 18 Jan 2022 13:11:54 -0800 Subject: [PATCH 30/30] fix import --- .../incomingcallsample/Controller/IncomingCallController.java | 2 +- .../com/communication/incomingcallsample/logger/Logger.java | 2 +- .../incomingcallsample/utils/IncomingCallHandler.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java index 57a956d4..c2983cbe 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/Controller/IncomingCallController.java @@ -18,7 +18,7 @@ import com.azure.messaging.eventgrid.systemevents.SubscriptionValidationEventData; import com.communication.incomingcallsample.EventHandler.EventAuthHandler; import com.communication.incomingcallsample.EventHandler.EventDispatcher; -import com.communication.incomingcallsample.Log.Logger; +import com.communication.incomingcallsample.logger.Logger; import com.communication.incomingcallsample.utils.CallConfiguration; import com.communication.incomingcallsample.utils.ConfigurationManager; import com.communication.incomingcallsample.utils.IncomingCallHandler; diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/logger/Logger.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/logger/Logger.java index 1e908476..085a04b7 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/logger/Logger.java +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/logger/Logger.java @@ -1,4 +1,4 @@ -package com.communication.incomingcallsample.log; +package com.communication.incomingcallsample.logger; import com.azure.messaging.eventgrid.EventGridEvent; diff --git a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java index 16afa8c9..54da6196 100644 --- a/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java +++ b/incomingcallsample/src/main/java/com/communication/incomingcallsample/utils/IncomingCallHandler.java @@ -35,7 +35,7 @@ import com.azure.cosmos.implementation.changefeed.CancellationTokenSource; import com.communication.incomingcallsample.EventHandler.EventDispatcher; import com.communication.incomingcallsample.EventHandler.NotificationCallback; -import com.communication.incomingcallsample.Log.Logger; +import com.communication.incomingcallsample.logger.Logger; public class IncomingCallHandler { private final String userIdentityRegex = "8:acs:[0-9a-fA-F]{8}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{12}_[0-9a-fA-F]{8}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{12}";